tag:blogger.com,1999:blog-86432398397124753642024-03-19T01:48:38.365-07:00SFDC StopAlways the latest about SalesforceRahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.comBlogger113125tag:blogger.com,1999:blog-8643239839712475364.post-91260035206252126972024-02-06T06:52:00.000-08:002024-02-06T07:13:35.992-08:00Repeater Component (Beta) in Salesforce Flow | Spring'24 ReleaseHello Trailblazers,<div><br /></div><div>In this post we're going to learn about new <b>Repeater</b> component provided by salesforce for screen flows. Let's begin!</div><div><br /></div><h2 style="text-align: left;">Screen Flow: Create contacts for Account</h2><div>Let's start with a basic use case: <b>We want to create multiple contacts for our account record in one go</b>. The standard screen flow approach for this will be: Show contact fields (contact form) on a screen, the user can fill those, maybe have a checkbox at the end which says, do you want to create another contact? If the user checks it, reload the same screen again on clicking next button so that the user can fill details for another contact. If the user doesn't check that checkbox, create all the contact records for which the user entered information till now.</div><div><br /></div><div>The drawback with this approach is: <b>User cannot view the details of all the contacts on a single screen while editing</b>. Due to this, he/she may go back and forth to update a particular contact which may lead to some other issues like: duplicate records (as you may be adding contact record to the list as well side by side - in the same loop which is used to repeat screen, with a plan that you can insert those at the end - give this a try) or there maybe some other issues too.</div><div><br /></div><div>We can solve these problems using the new <b>Repeater</b> component provided by salesforce for screen flows. Please note that this component is presently in <b>beta</b>.</div><div><h3>Creating the base flow with empty screen</h3><div>1. Go to <b>Setup</b> and search for <b>Flows</b>. Click on the <b>New Flow</b> button to create a new flow</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQsIFYG7EW5dsZo18eCZfGfhlOwi7gLwg26ne6Ws6w0oTamlQFaLHxKBiTShkLH9twofru8RsYPdY6EQIMnwgutJZ-kaeK5CYw4DHcHD6Lcr02uP09f8lvvgRl4bwrP2ScwCPF9QH9bbNYyWf3HtHulJ-24JDCC-91NuKOjKQ9QqaxkXxstGGROnnrjg/s2880/Screenshot%202024-01-29%20at%207.32.06%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1052" data-original-width="2880" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQsIFYG7EW5dsZo18eCZfGfhlOwi7gLwg26ne6Ws6w0oTamlQFaLHxKBiTShkLH9twofru8RsYPdY6EQIMnwgutJZ-kaeK5CYw4DHcHD6Lcr02uP09f8lvvgRl4bwrP2ScwCPF9QH9bbNYyWf3HtHulJ-24JDCC-91NuKOjKQ9QqaxkXxstGGROnnrjg/w640-h234/Screenshot%202024-01-29%20at%207.32.06%E2%80%AFPM.png" width="640" /></a></div>2. On the New Flow screen, choose <b>Screen Flow</b> and click on <b>Create</b> button<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-OTofFBltcsT3NIpKDjwX_yJcAfBcoUO8X0jWcbVNOP6FokMTyFfBwefPyu3HtO_hSZS-yDvlVbVKnpSHNLw0XjH2jT_pFShFAuMzGAWbzJhHcDXXsBzF4Nc8NKog8VgEbwgmF7fGlQFW5Yt35jzkAhCorlSKItIDmW5J2IXQJXKHOH_gyQ5YQuxhhEU/s2880/Screenshot%202024-01-29%20at%207.36.10%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1342" data-original-width="2880" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-OTofFBltcsT3NIpKDjwX_yJcAfBcoUO8X0jWcbVNOP6FokMTyFfBwefPyu3HtO_hSZS-yDvlVbVKnpSHNLw0XjH2jT_pFShFAuMzGAWbzJhHcDXXsBzF4Nc8NKog8VgEbwgmF7fGlQFW5Yt35jzkAhCorlSKItIDmW5J2IXQJXKHOH_gyQ5YQuxhhEU/w640-h298/Screenshot%202024-01-29%20at%207.36.10%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both;">3. Click on the + icon between <b>Start</b> and <b>End</b> element and choose <b>Screen</b> to add a screen element to our flow</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjFpWbXiy-6z3-HoPXTsrPYD0VbDtJITrsJf6x0n2xMboFECwaqcY0_c8JvyxgdeD-wwHs0mNaFEHFImaXvzQ0erjntNvho11tiEz8TnoOaGnaYa3dGCHSKoXC-tnZR37GHNqc1qQrWnSAMEupm7HoZsUO_SRLwo5riSHMtGEqIY2O9aBzAoVRKlIVAXM/s1162/Screenshot%202024-01-29%20at%207.38.49%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1162" data-original-width="1030" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjFpWbXiy-6z3-HoPXTsrPYD0VbDtJITrsJf6x0n2xMboFECwaqcY0_c8JvyxgdeD-wwHs0mNaFEHFImaXvzQ0erjntNvho11tiEz8TnoOaGnaYa3dGCHSKoXC-tnZR37GHNqc1qQrWnSAMEupm7HoZsUO_SRLwo5riSHMtGEqIY2O9aBzAoVRKlIVAXM/w355-h400/Screenshot%202024-01-29%20at%207.38.49%E2%80%AFPM.png" width="355" /></a></div>4. Add a label for the screen as <b>Create Contacts for Account</b>, the API name will auto populate to <b>Create_Contacts_for_Account</b>. Click on <b>Done</b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiN-O-M16vAC9kQjdmBTWqxU2GnI0n-d0ZoAIEI_RL9Bb9yOhVA2sC0-sK2xACxHsZbE5IRbg03M5fuRIg3U7gK4Cu7-nhCNmFuElt8J_0bNfEHI62rrlCtZ3L5YIPquSreiL9BbsbsmHsxb6hjz8qCIevO8-IQ-ff3a_ubW2fYLPnTSnumoBSiPtI1CTI/s2674/Screenshot%202024-02-04%20at%208.23.30%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1322" data-original-width="2674" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiN-O-M16vAC9kQjdmBTWqxU2GnI0n-d0ZoAIEI_RL9Bb9yOhVA2sC0-sK2xACxHsZbE5IRbg03M5fuRIg3U7gK4Cu7-nhCNmFuElt8J_0bNfEHI62rrlCtZ3L5YIPquSreiL9BbsbsmHsxb6hjz8qCIevO8-IQ-ff3a_ubW2fYLPnTSnumoBSiPtI1CTI/w640-h316/Screenshot%202024-02-04%20at%208.23.30%E2%80%AFPM.png" width="640" /></a></div></div><div>5. Before proceeding forward, let's save our flow. Click on the <b>Save</b> button at the top right corner. In the <b>Save the flow</b> popup, populate the Flow Label as <b>Create Contacts for Account</b>, the Flow API Name will autopopulate as <b>Create_Contacts_for_Account</b>. Click the <b>Save</b> button in the popup screen to save the flow.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxT5SdxpRXdrpU-6o181IagKzYY_88L_ZF9YEDXmh3AMUTZo86D5nEYBf8SbuL0F66rzgAxl9D7MdiNbGIIyMPkTnviUlIas4ejJjeTMB57hpjMxpxl0ONAT1nzR7iIhsZBcNLe84sQGSvTsW6Hxat1pqOzzuHHHJGiDm-_PXQgrYVTFYx8u7C04EEwqY/s1804/Screenshot%202024-02-04%20at%208.25.35%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="860" data-original-width="1804" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxT5SdxpRXdrpU-6o181IagKzYY_88L_ZF9YEDXmh3AMUTZo86D5nEYBf8SbuL0F66rzgAxl9D7MdiNbGIIyMPkTnviUlIas4ejJjeTMB57hpjMxpxl0ONAT1nzR7iIhsZBcNLe84sQGSvTsW6Hxat1pqOzzuHHHJGiDm-_PXQgrYVTFYx8u7C04EEwqY/w640-h306/Screenshot%202024-02-04%20at%208.25.35%E2%80%AFPM.png" width="640" /></a></div><div>6. Your flow will look as shown below</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIyFr5_KkqLzPKvmgHtXGRvaN_VfMGzblffpveFvlZ_qakGvi7jo9nGV6wLc2jW1i5chMz8GjFQyopjNcVs0plVA6fpQ3e_Bzpc5xwtYQDq9rls_lOCguD4Dy-2balbt-kLR6AkmT9lukBSWVLt6u1Sz49Go8WTQZZcjIjznW-qVaKTKi9jYUOlKMF_yg/s2880/Screenshot%202024-02-04%20at%208.27.25%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="942" data-original-width="2880" height="210" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIyFr5_KkqLzPKvmgHtXGRvaN_VfMGzblffpveFvlZ_qakGvi7jo9nGV6wLc2jW1i5chMz8GjFQyopjNcVs0plVA6fpQ3e_Bzpc5xwtYQDq9rls_lOCguD4Dy-2balbt-kLR6AkmT9lukBSWVLt6u1Sz49Go8WTQZZcjIjznW-qVaKTKi9jYUOlKMF_yg/w640-h210/Screenshot%202024-02-04%20at%208.27.25%E2%80%AFPM.png" width="640" /></a></div><h3 style="text-align: left;">Populate the screen with repeater component with contact fields inside it</h3><div>Open the screen element and add repeater component to it as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDaYUARbeSG4JiyDtMO_YPHC2MmA8bIYxZu7slRl69qRjx1ppIEns3lt3N9jMF4ADEc_MTQyoPh-OjH7nY_fbnmCsX2R6xo5Oh07CH9c5llmghK3gdnODAYLFiGOQYvQigsdWNsbP4ZjqBZb2E9bVhTVwS2Cho1PP3UtJXVx8RxUxeI2oWFcGqJNpLp1g/s2682/Screenshot%202024-02-04%20at%208.35.05%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1424" data-original-width="2682" height="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDaYUARbeSG4JiyDtMO_YPHC2MmA8bIYxZu7slRl69qRjx1ppIEns3lt3N9jMF4ADEc_MTQyoPh-OjH7nY_fbnmCsX2R6xo5Oh07CH9c5llmghK3gdnODAYLFiGOQYvQigsdWNsbP4ZjqBZb2E9bVhTVwS2Cho1PP3UtJXVx8RxUxeI2oWFcGqJNpLp1g/w640-h340/Screenshot%202024-02-04%20at%208.35.05%E2%80%AFPM.png" width="640" /></a></div>The API name for the same is: <b>ContactDetailsRepeater</b>. Now, let's add some contact fields in our repeater component.<div><br /></div><div>Let's add a text field as shown below, the Label of the field can be: <b>First Name</b> with the API Name as <b>First_Name</b>.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEuRT2e5xXH3atpoys7gB7AWB_B7qm7-sD3osq9UCmfQSwZ_tnuWL0S_QIAGYlg6ay2UDVg1TQNRej1Uc5hFOnNqWmWHtziz-W1HuRRFmA550mQuFbyYJypDtPNyENzv5YSxBKy-yfVPFLknOuH31mIUr0kTyy0-f4iGv4VCeVehDYXva5ir9HYwPy5es/s2692/Screenshot%202024-02-04%20at%208.37.53%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1310" data-original-width="2692" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEuRT2e5xXH3atpoys7gB7AWB_B7qm7-sD3osq9UCmfQSwZ_tnuWL0S_QIAGYlg6ay2UDVg1TQNRej1Uc5hFOnNqWmWHtziz-W1HuRRFmA550mQuFbyYJypDtPNyENzv5YSxBKy-yfVPFLknOuH31mIUr0kTyy0-f4iGv4VCeVehDYXva5ir9HYwPy5es/w640-h312/Screenshot%202024-02-04%20at%208.37.53%E2%80%AFPM.png" width="640" /></a></div>Similarly, let's add the last name field as well with Label as <b>Last Name</b> and API Name as <b>Last_Name</b>. We'll also make this field <b>Required</b> by checking the <b>Require</b> checkbox as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRyKrqi2079NWqNIABSTM2J9M9tb46IDOBoz-1TgZZqPugv9qQx5nJrERkI-IQ1767kmGDd53Z1-YcTNMknzqzpXxY6dcJaS_FVgMkad97FHfUU_IozrXQH8wFYvyj6rTCDxazssZ5APxHmJkSi8qD7gON61Mvtyi_coc7dB9rlmWX0dTlD0EUDQ9RQcw/s2656/Screenshot%202024-02-04%20at%208.41.07%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1306" data-original-width="2656" height="314" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRyKrqi2079NWqNIABSTM2J9M9tb46IDOBoz-1TgZZqPugv9qQx5nJrERkI-IQ1767kmGDd53Z1-YcTNMknzqzpXxY6dcJaS_FVgMkad97FHfUU_IozrXQH8wFYvyj6rTCDxazssZ5APxHmJkSi8qD7gON61Mvtyi_coc7dB9rlmWX0dTlD0EUDQ9RQcw/w640-h314/Screenshot%202024-02-04%20at%208.41.07%E2%80%AFPM.png" width="640" /></a></div>Click on <b>Done</b>.<div><h3 style="text-align: left;">Iterate contacts to create a contact list</h3><div>The repeater component will provide us a list of contact records. Let's iterate that list by using a loop. Click on the + icon again and add a loop element as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0QUv4PVgZ4PkMtGTLkNs5iFau2nQB7MYCu5PqjL37kUBsBlnbyZhCnB7jGURoYVbhhq_WqtJ51Pb35pHCydCuA_6UrkHRLsZ6hktw4odf6P2s3q5qWBgZM4jzGoMKfDtZo4Sqack1DELa7nOup4OOOWPrEmYL9zsXdCxwOCoIA1LUx1aEznjqii9UVmI/s1082/Screenshot%202024-02-04%20at%208.44.02%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="902" data-original-width="1082" height="534" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0QUv4PVgZ4PkMtGTLkNs5iFau2nQB7MYCu5PqjL37kUBsBlnbyZhCnB7jGURoYVbhhq_WqtJ51Pb35pHCydCuA_6UrkHRLsZ6hktw4odf6P2s3q5qWBgZM4jzGoMKfDtZo4Sqack1DELa7nOup4OOOWPrEmYL9zsXdCxwOCoIA1LUx1aEznjqii9UVmI/w640-h534/Screenshot%202024-02-04%20at%208.44.02%E2%80%AFPM.png" width="640" /></a></div>Loop Label can be <b>Iterate Contacts</b> and API name can be <b>Iterate_Contacts</b>. For the <b>Collection Variable</b> you can have it as <b>{!ContactDetailsRepeater.AllItems}</b> as shown in the below images:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpyn6dDL3ohWieEVl1jrnSLt8L7O5gU9lfgEqq5I4Cbbfu2Nsk52dqyQc3OYonSlfjcrsyZVmR_maWnt8AlaCtCJaw_aTOfxx5mH6A4SGe3ZGecUCsCPenVHt8nrj6Gow25eMOQeH_c14U7Vo61WrSXl_mIDv3toR6qF7SoTL0rbI__JkL7BRNI4lW8Z0/s2456/Screenshot%202024-02-04%20at%208.44.54%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1228" data-original-width="2456" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpyn6dDL3ohWieEVl1jrnSLt8L7O5gU9lfgEqq5I4Cbbfu2Nsk52dqyQc3OYonSlfjcrsyZVmR_maWnt8AlaCtCJaw_aTOfxx5mH6A4SGe3ZGecUCsCPenVHt8nrj6Gow25eMOQeH_c14U7Vo61WrSXl_mIDv3toR6qF7SoTL0rbI__JkL7BRNI4lW8Z0/w640-h320/Screenshot%202024-02-04%20at%208.44.54%E2%80%AFPM.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjifVGFZCB7-zy5u0TO5D-O6eUlG03wMO21gsDVySjeqwUVG-eQ9V4yb9wDuqXfLU5CuSFI9yP1aiuUNSrshCZTW406nNSG2ESDk1GtyPHGZU8KEDtA7Wcgo7yNN2YotNO3inldJVqtWpZ_5FgMawWOJcEN0JahROaA7SZqhcYsemDIj-kocjn1P3hQOXY/s828/Screenshot%202024-02-04%20at%208.46.02%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="510" data-original-width="828" height="246" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjifVGFZCB7-zy5u0TO5D-O6eUlG03wMO21gsDVySjeqwUVG-eQ9V4yb9wDuqXfLU5CuSFI9yP1aiuUNSrshCZTW406nNSG2ESDk1GtyPHGZU8KEDtA7Wcgo7yNN2YotNO3inldJVqtWpZ_5FgMawWOJcEN0JahROaA7SZqhcYsemDIj-kocjn1P3hQOXY/w400-h246/Screenshot%202024-02-04%20at%208.46.02%E2%80%AFPM.png" width="400" /></a></div>The final screen will look like this:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSrLDQWzQPFJzi-DPrBH8OI2qdYjLett_P9ffn7FglcaCTvGA-HROLekddDwmM9CA6bqjS85x47pk9isNjg7RLFud6cE8O8z6D4i3F0P8b9s_V660bpjLbg2iEFvOIlYW0YPt6EWrATUtldL-AdQJOw39JUw8c6kkTQpzGCENdRK8H8oLBdGqFhUpjJEI/s2880/Screenshot%202024-02-04%20at%208.47.58%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1448" data-original-width="2880" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSrLDQWzQPFJzi-DPrBH8OI2qdYjLett_P9ffn7FglcaCTvGA-HROLekddDwmM9CA6bqjS85x47pk9isNjg7RLFud6cE8O8z6D4i3F0P8b9s_V660bpjLbg2iEFvOIlYW0YPt6EWrATUtldL-AdQJOw39JUw8c6kkTQpzGCENdRK8H8oLBdGqFhUpjJEI/w640-h322/Screenshot%202024-02-04%20at%208.47.58%E2%80%AFPM.png" width="640" /></a></div>Now, let's add an assignment element inside the loop to create our contact record. Click on the circle (+ element) inside the loop and choose assignment as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNf9YsUZ71ChZNqJhAHhgm0hekh7ky4ZdURfft65O7DGemt4u5ezNUg1VGn5ptVm40ddcsEMb6P0rn_m2GR0NJ8wkOYMv4MfMVcLEi2VNJvdIRF1_yqMhxzyP62Kh7SRMM3uk0q4w-55VkwY_MnJP5bRL4yvCB0Ri1CunsnhhzwvXJMH2rb7SxU_uLCaI/s1222/Screenshot%202024-02-04%20at%208.50.05%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1222" data-original-width="1076" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNf9YsUZ71ChZNqJhAHhgm0hekh7ky4ZdURfft65O7DGemt4u5ezNUg1VGn5ptVm40ddcsEMb6P0rn_m2GR0NJ8wkOYMv4MfMVcLEi2VNJvdIRF1_yqMhxzyP62Kh7SRMM3uk0q4w-55VkwY_MnJP5bRL4yvCB0Ri1CunsnhhzwvXJMH2rb7SxU_uLCaI/w353-h400/Screenshot%202024-02-04%20at%208.50.05%E2%80%AFPM.png" width="353" /></a></div>Our assignment Label can be <b>Create Contact Record</b> and API name will be <b>Create_Contact_Record</b> as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhvutQyfSD2BDVp2blV-XkhC4Gfc9HxrY-kMrCq4HN79ErGkZ0Wkh99LAHRz6TjNT5hQF4BZWoSAz13lGLMJZUxU9bzoyoV6S0lNv1YtAzAwQPSqyXndUqiDFgdmS-YsAu2fkenYE_0YqKNVpjuyGqOlSF_YFYek3U2sA3ee4f_i9rfniOV1WwwzB9Q-Q/s2880/Screenshot%202024-02-04%20at%208.52.09%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1380" data-original-width="2880" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhvutQyfSD2BDVp2blV-XkhC4Gfc9HxrY-kMrCq4HN79ErGkZ0Wkh99LAHRz6TjNT5hQF4BZWoSAz13lGLMJZUxU9bzoyoV6S0lNv1YtAzAwQPSqyXndUqiDFgdmS-YsAu2fkenYE_0YqKNVpjuyGqOlSF_YFYek3U2sA3ee4f_i9rfniOV1WwwzB9Q-Q/w640-h306/Screenshot%202024-02-04%20at%208.52.09%E2%80%AFPM.png" width="640" /></a></div>Now, to create a new contact and add it to the list, we need some resources. Let's create a new resource named <b>Contact</b> which will store the contact record as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYf9HC1TmuoKQagNXcwEGFZj-PTPndmNBhw8bQW54aWkjaNMd24BKdqtNL2UAsmNMaJ4u-B2UMZMBlnrz9LWu-dppzpYhykV3Tio1_ScslIA1EsvzY_jXKvAL4SFPts8tah_lfL-58AKAGHr0-pqfh6KVnIrnX5YBIOWQkyET_J-I-Aeosy1xA2rGAKts/s2550/Screenshot%202024-02-04%20at%2010.49.22%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1352" data-original-width="2550" height="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYf9HC1TmuoKQagNXcwEGFZj-PTPndmNBhw8bQW54aWkjaNMd24BKdqtNL2UAsmNMaJ4u-B2UMZMBlnrz9LWu-dppzpYhykV3Tio1_ScslIA1EsvzY_jXKvAL4SFPts8tah_lfL-58AKAGHr0-pqfh6KVnIrnX5YBIOWQkyET_J-I-Aeosy1xA2rGAKts/w640-h340/Screenshot%202024-02-04%20at%2010.49.22%E2%80%AFPM.png" width="640" /></a></div>Resource Type: <b>Variable</b><div>API Name: <b>Contact</b></div><div>Data Type: <b>Record</b></div><div>Object: <b>Contact</b></div><div><br /></div><div>Click on <b>Done</b> and create another resource to store list of contacts as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVn1535SZ6KyDrQUaieoubeEMmlOaIF5Bfnj4ky54BeXZ03Yamz8xBJtBmb-be-wrzY3ejMKq6i1pUt6UkA_2KqZsHWn-aq2J6mnT08Epg2rgHOIYU5gXbcL0J3wT0qL1kd4kmI9h51zzjXpXV1V67wrZ-HDbW6EC4eqQoFMCqxOendWEBGCkfXdqD6AQ/s1838/Screenshot%202024-02-04%20at%2010.51.56%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1306" data-original-width="1838" height="454" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVn1535SZ6KyDrQUaieoubeEMmlOaIF5Bfnj4ky54BeXZ03Yamz8xBJtBmb-be-wrzY3ejMKq6i1pUt6UkA_2KqZsHWn-aq2J6mnT08Epg2rgHOIYU5gXbcL0J3wT0qL1kd4kmI9h51zzjXpXV1V67wrZ-HDbW6EC4eqQoFMCqxOendWEBGCkfXdqD6AQ/w640-h454/Screenshot%202024-02-04%20at%2010.51.56%E2%80%AFPM.png" width="640" /></a></div>Resource Type: <b>Variable</b><div>API Name: <b>ContactList</b></div><div>Data Type: <b>Record</b></div><div>Object: <b>Contact</b></div><div>Allow multiple values (collection): <b>True</b></div><div><br /></div><div>Click on <b>Done</b>. We also want to associate our contacts to the account record. Let's create another resource which will store the id of the account as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKJvX0VbCwWaTw1BriaIaowaASv2iIauuv0Eao5fC2-b8aHd8a0lAa-atdmVTJQYgtnLGt2gS9-LXkB7YSusCP9aZ-XYcgZIaBqgOnKuOVIRQVe3hQU4xDXtD4SWAHjcnQG0lmntCQebedJi4f8uwSpwzH-cVFM-e2M4bl5aAY5BABKZXeFZ1kubDyrTo/s1820/Screenshot%202024-02-04%20at%2010.54.42%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1306" data-original-width="1820" height="460" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKJvX0VbCwWaTw1BriaIaowaASv2iIauuv0Eao5fC2-b8aHd8a0lAa-atdmVTJQYgtnLGt2gS9-LXkB7YSusCP9aZ-XYcgZIaBqgOnKuOVIRQVe3hQU4xDXtD4SWAHjcnQG0lmntCQebedJi4f8uwSpwzH-cVFM-e2M4bl5aAY5BABKZXeFZ1kubDyrTo/w640-h460/Screenshot%202024-02-04%20at%2010.54.42%E2%80%AFPM.png" width="640" /></a></div>Resource Type: <b>Variable</b><div>API Name: <b>AccountId</b></div><div>Data Type: <b>Text</b></div><div>Availability Outside the Flow: <b>Available for input</b></div><div><b><br /></b></div><div>Click on <b>Done</b> and let's update our assignment element as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyo121yOsJT1mMiC4QjtpwunHR7OZXSGOlaZf1htWrGd-Px8XKwdz16lB0Z_ShDttKL7jFymCSVXIOqqpZrOpmQnAxV9As3cXhSW3NTi75jxppmxoDv5nmdraehUqxJl8rvUhy0kQuRKviPutCYZD1-ihIEjI6KpxjvebSU-20Tuj3vI0wlmakycKnefw/s1612/Screenshot%202024-02-04%20at%2011.00.32%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1270" data-original-width="1612" height="504" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyo121yOsJT1mMiC4QjtpwunHR7OZXSGOlaZf1htWrGd-Px8XKwdz16lB0Z_ShDttKL7jFymCSVXIOqqpZrOpmQnAxV9As3cXhSW3NTi75jxppmxoDv5nmdraehUqxJl8rvUhy0kQuRKviPutCYZD1-ihIEjI6KpxjvebSU-20Tuj3vI0wlmakycKnefw/w640-h504/Screenshot%202024-02-04%20at%2011.00.32%E2%80%AFPM.png" width="640" /></a></div>{!Contact.FirstName} <b>Equals</b> {!Iterate_Contacts.First_Name}<div>{!Contact.LastName} <b>Equals</b> {!Iterate_Contacts.Last_Name}</div><div>{!Contact.AccountId} <b>Equals</b> {!AccountId}</div><div>{!ContactList} <b>Add</b> {!Contact}<br /><div><br /></div><div>We assigned the first name and last name values from current item of the list provided by repeater to the first name and last name of the contact record. Then we assigned the value of AccountId in our contact record to the value of AccountId variable. Finally, we added our contact record to the ContactList.</div><h3 style="text-align: left;">Inserting Contacts in Salesforce</h3><div>Now, we need to insert this list, click on the circle (+ element) outside the loop and choose Create Records element as shown below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiErqxEm-ZSIQXbKSKSvbJ0rbWYKHrXuLtZVoghiCaVt4JeyHr-V2ao9BP6uSqSW54jXVcHxY7SqSt4rMdXeiisdSDDKiaieWHpQVumspcegfYi3vVP1tENkc7RJuXJM3iSTt1MrDcQRq72SmSsTfyHjaMzEG2c8ai24TGPiIF2aS0r7eLZjssq1_WjMSc/s1092/Screenshot%202024-02-04%20at%2011.08.33%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1092" data-original-width="972" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiErqxEm-ZSIQXbKSKSvbJ0rbWYKHrXuLtZVoghiCaVt4JeyHr-V2ao9BP6uSqSW54jXVcHxY7SqSt4rMdXeiisdSDDKiaieWHpQVumspcegfYi3vVP1tENkc7RJuXJM3iSTt1MrDcQRq72SmSsTfyHjaMzEG2c8ai24TGPiIF2aS0r7eLZjssq1_WjMSc/w356-h400/Screenshot%202024-02-04%20at%2011.08.33%E2%80%AFPM.png" width="356" /></a></div>In the <b>New Create Records</b> section, add the label as <b>Create Contacts</b>, the API name as: <b>Create_Contacts</b>. The value for <b>How Many Records to Create</b> should be <b>Multiple</b> and in the <b>Record Collection</b> field, we can choose our <b>ContactList</b> collection as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9aEKq_i21mTMpaTkVzfqRybEOCm8IDgXcuwEKJwn4xd2Ntp3Q-w5YQD_PkoMogBXAklRbGvAXs0SAatuEMha21LHTzyRTGmROoCls0WRnw-Eijm7PpVt278Xh_vXUOg_aHWpjmfSG5CSg77-dKLtH6tV3vEBmIQXEbydEzuXbHnR_9f5tkQfxwu2N9XY/s2792/Screenshot%202024-02-04%20at%2011.09.59%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1458" data-original-width="2792" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9aEKq_i21mTMpaTkVzfqRybEOCm8IDgXcuwEKJwn4xd2Ntp3Q-w5YQD_PkoMogBXAklRbGvAXs0SAatuEMha21LHTzyRTGmROoCls0WRnw-Eijm7PpVt278Xh_vXUOg_aHWpjmfSG5CSg77-dKLtH6tV3vEBmIQXEbydEzuXbHnR_9f5tkQfxwu2N9XY/w640-h334/Screenshot%202024-02-04%20at%2011.09.59%E2%80%AFPM.png" width="640" /></a></div>Now, we can save and activate the flow. Let's go to our account record's detail page and embed the flow there.</div><h3 style="text-align: left;">Adding Screen Flow to Lightning Record Page</h3><div>Go to any account record in salesforce. Click on Gear icon and choose <b>Edit Page</b>.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1AsiBP2wR0BZ1CxYMV56pfD4D_4evB7WnMtcWRqQfZN06HxxhvNgH5BrY80dGyNmgG9BixFm7jg2XVJwUQu5aOPERLPHu7n86wYxUGsRLI4ZCcNOHb4wfKrH2hxml36LZsLqcz6ySgxACq6H2iAuRtbcr91PQxelVHC6DI03EPaMKsDgto5Z2PoE351Y/s2880/Screenshot%202024-02-04%20at%2011.33.13%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="666" data-original-width="2880" height="148" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1AsiBP2wR0BZ1CxYMV56pfD4D_4evB7WnMtcWRqQfZN06HxxhvNgH5BrY80dGyNmgG9BixFm7jg2XVJwUQu5aOPERLPHu7n86wYxUGsRLI4ZCcNOHb4wfKrH2hxml36LZsLqcz6ySgxACq6H2iAuRtbcr91PQxelVHC6DI03EPaMKsDgto5Z2PoE351Y/w640-h148/Screenshot%202024-02-04%20at%2011.33.13%E2%80%AFPM.png" width="640" /></a></div><div>In the <b>Lightning App Builder</b>, search for <b>Flow</b> in the components section in the left, drag and drop it anywhere on the page you like. In my case, I've added it to the right. Choose our <b>Create Contacts for Account</b> flow and for the <b>AccountId</b> variable, check the <b>Pass record ID to this variable</b> checkbox as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhanb22c87TKvigdFDr8i86l0uEStuvvHEuTK6HoGUlLEGMf997ekwEu5-f5FQ6t7AGv9XfnB_TaG3ziBNesoH8U6XkpfPuBlczfMhTyGtmM1Q5nhl-xed4_ES0VEXbyD9lExt0W7QZC-zl5Tf_qWYQdNvNe1Igzel1uprSQn5UnsBM5Txo1HuWYd2on9U/s2880/Screenshot%202024-02-04%20at%2011.31.17%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1502" data-original-width="2880" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhanb22c87TKvigdFDr8i86l0uEStuvvHEuTK6HoGUlLEGMf997ekwEu5-f5FQ6t7AGv9XfnB_TaG3ziBNesoH8U6XkpfPuBlczfMhTyGtmM1Q5nhl-xed4_ES0VEXbyD9lExt0W7QZC-zl5Tf_qWYQdNvNe1Igzel1uprSQn5UnsBM5Txo1HuWYd2on9U/w640-h334/Screenshot%202024-02-04%20at%2011.31.17%E2%80%AFPM.png" width="640" /></a></div>Click on <b>Save</b>. <b>Activate</b> the page if needed and go back. You'll notice that the flow is present on the page as shown below:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVHb8KZPtUroehmttp-zxLNPvs14rjUU3F5sFZoqCPitFXUJig6Milf5PWoerRlSNkA_BBditp9PjmRZXnJnHsHq4Whxtk_zbzbaqu89kzhtfRupvhZGt5Q2g0OV4cbwonwclrLdHpVorI2GMer1nIfGe16ANceILgDLHewXqDA-SLvxq7aYhtLuqFvZE/s2880/Screenshot%202024-02-04%20at%2011.43.33%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1308" data-original-width="2880" height="290" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVHb8KZPtUroehmttp-zxLNPvs14rjUU3F5sFZoqCPitFXUJig6Milf5PWoerRlSNkA_BBditp9PjmRZXnJnHsHq4Whxtk_zbzbaqu89kzhtfRupvhZGt5Q2g0OV4cbwonwclrLdHpVorI2GMer1nIfGe16ANceILgDLHewXqDA-SLvxq7aYhtLuqFvZE/w640-h290/Screenshot%202024-02-04%20at%2011.43.33%E2%80%AFPM.png" width="640" /></a></div><div><h3 style="text-align: left;">Demo</h3><div>Our changes are done. Let's take a look at the demo:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6hofzpARwmapuhVoE12GXXHZQQsrMFP6YbRKRuqcGRB2z7HbOUvRLnF4GvGltRIR8lIpPj6IKqc_8co2KjWxM-qlspdqVMgXsuIh1Td0Vd25Xvs1oqg_SX-GnqqKGDw2Ujj25PkIaxdSbH5hxK-zZF8gbPbKLgs2hfmksPWYgoH4I9_d_TdbeTDqFeGA/s1440/CPT2402062032-1440x683.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="683" data-original-width="1440" height="304" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6hofzpARwmapuhVoE12GXXHZQQsrMFP6YbRKRuqcGRB2z7HbOUvRLnF4GvGltRIR8lIpPj6IKqc_8co2KjWxM-qlspdqVMgXsuIh1Td0Vd25Xvs1oqg_SX-GnqqKGDw2Ujj25PkIaxdSbH5hxK-zZF8gbPbKLgs2hfmksPWYgoH4I9_d_TdbeTDqFeGA/w640-h304/CPT2402062032-1440x683.gif" width="640" /></a></div>As you can see above, as we click on the <b>+ Add</b> button, the section inside the repeater component is repeated so that we can create multiple contacts in one go. Similarly, we can also click the <b>Remove</b> button to remove a section. As we clicked <b>Next</b> button, 2 contacts: <b>Richard Hendricks and Erlich Bachman</b> were created and attached to the current account record.</div><div><br /></div><div><b>Note</b>: The Repeater component is currently in <b>Beta</b> and supports these components inside it as provided by salesforce: Checkbox, Checkbox Group, Currency, Date, Date & Time, Long Text Area, Multi-Select Picklist, Number, Password, Picklist, Radio Buttons, Text, and Display Text</div><div><br /></div><div>That's all for this tutorial everyone, I hope you liked it, let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-63826605651299474672024-01-31T07:30:00.000-08:002024-01-31T07:30:00.143-08:00Display calculated information on the same screen in screen flow | Use Display Text as Reactive Component | Spring'24 ReleaseHello Trailblazers,<div><br /></div><div>Salesforce recently <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_automate_flow_builder_use_more_components_to_react_to_changes_on_the_same_screen_generally_available.htm&release=248&type=5" target="_blank"><span style="color: #2b00fe;">allowed some more components to react to changes</span></a> happenning on the same screen in a screen flow. In this post, we're going to take a look at an example to understand this update.</div><div><br /></div><h2 style="text-align: left;">Screen Flow: Time Log</h2><div>Let's say you're working on an HR Application created in salesforce. You want your company's employees to add/update their weekly time log on a screen. Let's create a screen flow for the same.</div><h3 style="text-align: left;">Creating the base flow with empty screen</h3><div>1. Go to <b>Setup</b> and search for <b>Flows</b>. Click on the <b>New Flow</b> button to create a new flow</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQsIFYG7EW5dsZo18eCZfGfhlOwi7gLwg26ne6Ws6w0oTamlQFaLHxKBiTShkLH9twofru8RsYPdY6EQIMnwgutJZ-kaeK5CYw4DHcHD6Lcr02uP09f8lvvgRl4bwrP2ScwCPF9QH9bbNYyWf3HtHulJ-24JDCC-91NuKOjKQ9QqaxkXxstGGROnnrjg/s2880/Screenshot%202024-01-29%20at%207.32.06%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1052" data-original-width="2880" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFQsIFYG7EW5dsZo18eCZfGfhlOwi7gLwg26ne6Ws6w0oTamlQFaLHxKBiTShkLH9twofru8RsYPdY6EQIMnwgutJZ-kaeK5CYw4DHcHD6Lcr02uP09f8lvvgRl4bwrP2ScwCPF9QH9bbNYyWf3HtHulJ-24JDCC-91NuKOjKQ9QqaxkXxstGGROnnrjg/w640-h234/Screenshot%202024-01-29%20at%207.32.06%E2%80%AFPM.png" width="640" /></a></div>2. On the New Flow screen, choose <b>Screen Flow</b> and click on <b>Create</b> button<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-OTofFBltcsT3NIpKDjwX_yJcAfBcoUO8X0jWcbVNOP6FokMTyFfBwefPyu3HtO_hSZS-yDvlVbVKnpSHNLw0XjH2jT_pFShFAuMzGAWbzJhHcDXXsBzF4Nc8NKog8VgEbwgmF7fGlQFW5Yt35jzkAhCorlSKItIDmW5J2IXQJXKHOH_gyQ5YQuxhhEU/s2880/Screenshot%202024-01-29%20at%207.36.10%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1342" data-original-width="2880" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-OTofFBltcsT3NIpKDjwX_yJcAfBcoUO8X0jWcbVNOP6FokMTyFfBwefPyu3HtO_hSZS-yDvlVbVKnpSHNLw0XjH2jT_pFShFAuMzGAWbzJhHcDXXsBzF4Nc8NKog8VgEbwgmF7fGlQFW5Yt35jzkAhCorlSKItIDmW5J2IXQJXKHOH_gyQ5YQuxhhEU/w640-h298/Screenshot%202024-01-29%20at%207.36.10%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">3. Click on the + icon between <b>Start</b> and <b>End</b> element and choose <b>Screen</b> to add a screen element to our flow</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjFpWbXiy-6z3-HoPXTsrPYD0VbDtJITrsJf6x0n2xMboFECwaqcY0_c8JvyxgdeD-wwHs0mNaFEHFImaXvzQ0erjntNvho11tiEz8TnoOaGnaYa3dGCHSKoXC-tnZR37GHNqc1qQrWnSAMEupm7HoZsUO_SRLwo5riSHMtGEqIY2O9aBzAoVRKlIVAXM/s1162/Screenshot%202024-01-29%20at%207.38.49%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1162" data-original-width="1030" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjFpWbXiy-6z3-HoPXTsrPYD0VbDtJITrsJf6x0n2xMboFECwaqcY0_c8JvyxgdeD-wwHs0mNaFEHFImaXvzQ0erjntNvho11tiEz8TnoOaGnaYa3dGCHSKoXC-tnZR37GHNqc1qQrWnSAMEupm7HoZsUO_SRLwo5riSHMtGEqIY2O9aBzAoVRKlIVAXM/w355-h400/Screenshot%202024-01-29%20at%207.38.49%E2%80%AFPM.png" width="355" /></a></div>4. Add a label for the screen as <b>Time Log</b>, the API name will auto populate to <b>Time_Log</b>. Click on <b>Done</b><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-9VqkW2RJa_qgixfjpfoVb2ClIP5jzabSuYYN5Osk2NiHAZzos2IHOp5cYRGYXPBUNxLULO9f8jX-DIV3RYtbO5M62548z5LeFI3nnVd1iHlhXvNlPBb97tI1ZHzUMZDREvFq5c6paPbABacyFH4IwdSFAL00kRBfw0Cqrrm_JLGzrCpeQsYWQ-1wZes/s2696/Screenshot%202024-01-29%20at%207.41.41%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1286" data-original-width="2696" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-9VqkW2RJa_qgixfjpfoVb2ClIP5jzabSuYYN5Osk2NiHAZzos2IHOp5cYRGYXPBUNxLULO9f8jX-DIV3RYtbO5M62548z5LeFI3nnVd1iHlhXvNlPBb97tI1ZHzUMZDREvFq5c6paPbABacyFH4IwdSFAL00kRBfw0Cqrrm_JLGzrCpeQsYWQ-1wZes/w640-h306/Screenshot%202024-01-29%20at%207.41.41%E2%80%AFPM.png" width="640" /></a></div>5. Before proceeding forward, let's save our flow. Click on the <b>Save</b> button at the top right corner. In the <b>Save the flow</b> popup, populate the Flow Label as <b>Time Log</b>, the Flow API Name will autopopulate as <b>Time_Log</b>. Click the <b>Save</b> button in the popup screen to save the flow.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhks4DEaNaMAcxmt6qCLrZWrIZuNFzJlfmJYl2LmPKE_UmZQk1YKaviHcQytzmI22p0ExW3lnRCBds20fyekQLORJaBn4c2uLdKmf2TClSTaOjpDkoSRqtbCCXejzDg9y7qp9pR8Pp3931zNgpJ2oddAKnNkxtL-nciieMc2OP7xuMvEdaBSfinMqACvQY/s2880/Screenshot%202024-01-29%20at%207.44.24%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1204" data-original-width="2880" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhks4DEaNaMAcxmt6qCLrZWrIZuNFzJlfmJYl2LmPKE_UmZQk1YKaviHcQytzmI22p0ExW3lnRCBds20fyekQLORJaBn4c2uLdKmf2TClSTaOjpDkoSRqtbCCXejzDg9y7qp9pR8Pp3931zNgpJ2oddAKnNkxtL-nciieMc2OP7xuMvEdaBSfinMqACvQY/w640-h268/Screenshot%202024-01-29%20at%207.44.24%E2%80%AFPM.png" width="640" /></a></div>6. Your flow will look as shown below<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9vWXXxVvWpIMIAgLJHtawcy7Zyy9eTjBLu9gPUC7i3ZR8qShIDvhccllbcGMIy23qdDx0_Oz3l4-LN1qI_mX-9ojX6piUJLV4KXY7CbV-mDAyoWGaebaobxwOzI2JvecYjswB4pHh0x-pC0llVgdax5xi_Ks6m_JyrYNS60J6jSlY-rbHV1xdMEpAH0M/s2880/Screenshot%202024-01-29%20at%207.47.48%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="892" data-original-width="2880" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9vWXXxVvWpIMIAgLJHtawcy7Zyy9eTjBLu9gPUC7i3ZR8qShIDvhccllbcGMIy23qdDx0_Oz3l4-LN1qI_mX-9ojX6piUJLV4KXY7CbV-mDAyoWGaebaobxwOzI2JvecYjswB4pHh0x-pC0llVgdax5xi_Ks6m_JyrYNS60J6jSlY-rbHV1xdMEpAH0M/w640-h198/Screenshot%202024-01-29%20at%207.47.48%E2%80%AFPM.png" width="640" /></a></div><h3 style="text-align: left;">Populating the flow screen with input fields and reactive elements</h3><div>Now, it's time to populate our flow with some input fields. Let's edit our Time Log screen element and add some fields there as shown below:</div><div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieo7RG8LoK6v05E5nmwvCl35em9ezJuFLaLvA-xT8dYc8xpRwFrD6RfwHGR5uL0AiJdeK2ZKKBs0CRya-Lvc_Mzw5Q5aR12dKlG8GnPX5FuHWPsNDLExUSKoG2aH4a4lFyRon7kjytHJs7kHxjb_RnQHjvihA1DxXecSBvdrzSwlISxHcZmDLOE4StsFs/s2710/Screenshot%202024-01-31%20at%206.20.03%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1320" data-original-width="2710" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieo7RG8LoK6v05E5nmwvCl35em9ezJuFLaLvA-xT8dYc8xpRwFrD6RfwHGR5uL0AiJdeK2ZKKBs0CRya-Lvc_Mzw5Q5aR12dKlG8GnPX5FuHWPsNDLExUSKoG2aH4a4lFyRon7kjytHJs7kHxjb_RnQHjvihA1DxXecSBvdrzSwlISxHcZmDLOE4StsFs/w640-h312/Screenshot%202024-01-31%20at%206.20.03%E2%80%AFPM.png" width="640" /></a></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1Te_S9o7fnYMn8bEPlqX9VTEmkwBAH7TTdPn2bdM_ZYthTP1CL_aROab39mCfg-w8K8lWPCXlQNYN66QTK38bqwaOAGljHYs5cIGFWIHR3M7X8FtjhzRH3CCfQG1Qeg7sA4u5IgL_7RQ879C5vkQ6zblgkpb1q2fqLIGbdsivDlXQKyj37k5-60hVcWE/s2694/Screenshot%202024-01-31%20at%206.20.57%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1326" data-original-width="2694" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1Te_S9o7fnYMn8bEPlqX9VTEmkwBAH7TTdPn2bdM_ZYthTP1CL_aROab39mCfg-w8K8lWPCXlQNYN66QTK38bqwaOAGljHYs5cIGFWIHR3M7X8FtjhzRH3CCfQG1Qeg7sA4u5IgL_7RQ879C5vkQ6zblgkpb1q2fqLIGbdsivDlXQKyj37k5-60hVcWE/w640-h316/Screenshot%202024-01-31%20at%206.20.57%E2%80%AFPM.png" width="640" /></a></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyvn39SPd5mwcz2L-aKPH-Qc3cdzJy0Y0aeAz6eG3BC6L0p-3PoaraEiYMYQ9Zyl-lYBluTIyEFHMpIFIXbZ4Xzov-AOUeUBdFCdN1n30AFb6QXCMI9vugX-gUw1hOXOubtAjM4tIIbPvyr4dtgkCMKw8e1_ZUFbZEPMOTLZN3REbFvg6DEV9ZzOYereI/s2684/Screenshot%202024-01-31%20at%206.21.37%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1306" data-original-width="2684" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyvn39SPd5mwcz2L-aKPH-Qc3cdzJy0Y0aeAz6eG3BC6L0p-3PoaraEiYMYQ9Zyl-lYBluTIyEFHMpIFIXbZ4Xzov-AOUeUBdFCdN1n30AFb6QXCMI9vugX-gUw1hOXOubtAjM4tIIbPvyr4dtgkCMKw8e1_ZUFbZEPMOTLZN3REbFvg6DEV9ZzOYereI/w640-h312/Screenshot%202024-01-31%20at%206.21.37%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQPP8lvOxJCTIi4bHv_q9VQAtawX34ltGQTAG7IatVOpcLNTXINoPCOOV9Yw_dFEO6TO0dAaQFwU8gHnOVfP8W7Fmx7Wh-kIAtufBwHWqZhHKdNd0zLpaqFXptTVE4oC7HcNtl6RvQk2rVy_4JgZemj54KC3o47Sie-j9CFz1Ho0drvBKIObZ2tz1aZm4/s2708/Screenshot%202024-01-31%20at%206.22.22%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1308" data-original-width="2708" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQPP8lvOxJCTIi4bHv_q9VQAtawX34ltGQTAG7IatVOpcLNTXINoPCOOV9Yw_dFEO6TO0dAaQFwU8gHnOVfP8W7Fmx7Wh-kIAtufBwHWqZhHKdNd0zLpaqFXptTVE4oC7HcNtl6RvQk2rVy_4JgZemj54KC3o47Sie-j9CFz1Ho0drvBKIObZ2tz1aZm4/w640-h310/Screenshot%202024-01-31%20at%206.22.22%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1a3at5VpMppHZPfqWP3M7psov5sYbzIyoixJMXJaR-7DznLvWVUyNT9zZz-PtEydHP4LFM_8DGIh1nRnx8Il1HZdr6nwCosjV-ggbQ5i5G7zmMZEWu3fBTIdno2JRmA9HBX-K2DfJK2rOr3EVS7BJXlQmM7vAvrAqwqtUjb30K9bKo9jEk9pZKoekwGo/s2680/Screenshot%202024-01-31%20at%206.23.10%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1324" data-original-width="2680" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1a3at5VpMppHZPfqWP3M7psov5sYbzIyoixJMXJaR-7DznLvWVUyNT9zZz-PtEydHP4LFM_8DGIh1nRnx8Il1HZdr6nwCosjV-ggbQ5i5G7zmMZEWu3fBTIdno2JRmA9HBX-K2DfJK2rOr3EVS7BJXlQmM7vAvrAqwqtUjb30K9bKo9jEk9pZKoekwGo/w640-h316/Screenshot%202024-01-31%20at%206.23.10%E2%80%AFPM.png" width="640" /></a></div><div><br /></div><div>As you can see above, we added 5 number fields to fill the time log for each day. The labels and API names for the same are: <b>Monday</b>, <b>Tuesday</b>, <b>Wednesday</b>, <b>Thursday</b> and <b>Friday</b>. Now, we're going to add a display text which will be reactive as it'll refer to the number fields we created above. The display text element configuration is provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB2XwlhGWu4ka6Qc9my3-G4eWpIJXPdYkctJlNA82yWm4RV857w_laxHOAXfelqUtT4fSfm4p5X5XciMk-iqb6kcCuUaqODk0wnaO6tIshn6GZNLrDTporACgj6kGt86AUXfJkAektutWDF4quCJdN9DM7GbRsNPhhEG0mfCJc5M73RYuQz63iGT27JNA/s2712/Screenshot%202024-01-31%20at%206.28.40%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1302" data-original-width="2712" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB2XwlhGWu4ka6Qc9my3-G4eWpIJXPdYkctJlNA82yWm4RV857w_laxHOAXfelqUtT4fSfm4p5X5XciMk-iqb6kcCuUaqODk0wnaO6tIshn6GZNLrDTporACgj6kGt86AUXfJkAektutWDF4quCJdN9DM7GbRsNPhhEG0mfCJc5M73RYuQz63iGT27JNA/w640-h308/Screenshot%202024-01-31%20at%206.28.40%E2%80%AFPM.png" width="640" /></a></div>This Display Text is showing us the values entered in all the 5 fields above. It's name is <b>ReactiveDisplayText</b> and the value is provided below:<pre>Hours worked on Monday = {!Monday}
Hours worked on Tuesday = {!Tuesday}
Hours worked on Wednesday = {!Wednesday}
Hours worked on Thursday = {!Thursday}
Hours worked on Friday = {!Friday}
Total Hours =</pre><div>If you notice above, for <b>Total Hours</b>, we haven't referred to any resource yet. This is because we don't have one for this. Click on <b>Done</b> and let's add another resource in our flow named <b>Total</b> which will consist of the sum of all the hours entered in these fields. The details are provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV_fAOY0WvWKjzalqdaV5-IokEDrgGplsmc1F7j7U0aehnpyNB6wjf8V8eBm6bPTpzM_PI6gCeS_X6IV-_iWDDFi9Fg9c0k_q8OfVpznDwG-EF0KnYsMB_wfcayRxiKTA4aBb0vNFOtXPfP8eV9PAFBepcbTeiy0tYjNmTP4mgYPCdvjTw6kocCOmmm9k/s2386/Screenshot%202024-01-31%20at%206.36.47%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1536" data-original-width="2386" height="412" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV_fAOY0WvWKjzalqdaV5-IokEDrgGplsmc1F7j7U0aehnpyNB6wjf8V8eBm6bPTpzM_PI6gCeS_X6IV-_iWDDFi9Fg9c0k_q8OfVpznDwG-EF0KnYsMB_wfcayRxiKTA4aBb0vNFOtXPfP8eV9PAFBepcbTeiy0tYjNmTP4mgYPCdvjTw6kocCOmmm9k/w640-h412/Screenshot%202024-01-31%20at%206.36.47%E2%80%AFPM.png" width="640" /></a></div>If you notice above, we clicked the <b>New Resource</b> button and created a new resource named <b>Total</b> with the details provided below:<div><br /><div><b>Resource Type</b>: Formula</div><div><b>API Name</b>: Total</div><div><b>Description</b>: This formula will store the total hours, employee has worked in this week</div><div><b>Data Type</b>: Number</div><div><b>Decimal Places</b>: 0</div><div><b>Formula</b>: NULLVALUE({!Monday},0) + NULLVALUE({!Tuesday},0) + NULLVALUE({!Wednesday},0) + NULLVALUE({!Thursday},0) + NULLVALUE({!Friday},0)</div><div><br /><div>In this formula, we're adding values of all the fields: <b>Monday, Tuesday, Wednesday, Thursday and Friday</b>. We're wrapping each field value in <b>NULLVALUE()</b>, so that - in case, the value of any field is NULL (or let's say we haven't filled the field yet), it will be replaced by 0 in our formula calculation.<br /><div><br /></div><div>Click on <b>Done</b>.</div><div><br /></div><div>Now, let's get back to our screen and refer to the <b>Total</b> variable there as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6BG4egZrlO3YrLnP2jeQcsCoR7r_57NZiBzoPU9MslastSCDrl1A9uTv7KCmPjGJpd-CX0HosCsdpYNgLbzn1bWsPPgR2bqR8bNULM53Alje6E9-GrrzOZJQ8Xttpm0hLtJOBcqKqn3yaD5d1KAtF6_Vd1Q9WI50hX9NuwPAku1RRoC0SULSPImR-aQQ/s2720/Screenshot%202024-01-31%20at%206.43.15%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1430" data-original-width="2720" height="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6BG4egZrlO3YrLnP2jeQcsCoR7r_57NZiBzoPU9MslastSCDrl1A9uTv7KCmPjGJpd-CX0HosCsdpYNgLbzn1bWsPPgR2bqR8bNULM53Alje6E9-GrrzOZJQ8Xttpm0hLtJOBcqKqn3yaD5d1KAtF6_Vd1Q9WI50hX9NuwPAku1RRoC0SULSPImR-aQQ/w640-h336/Screenshot%202024-01-31%20at%206.43.15%E2%80%AFPM.png" width="640" /></a></div>We placed our cursor after "<b>Total Hours =</b>" text, searched for <b>Total</b> in our Resource Picker and clicked on this option as it appears. The updated value for our display text is shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij2LhwEvpBLnVLq0HyMGUOkQRMQYxdhrGKDsE8zcT7iBgfT7gO-MyYgAgmVBzNKoMsNJxaMG8uhBi146APXOYG8r_MpWaDGknD_ERg_Iv4gz5tqIWlvs1nSyvjobfzNlya45_DsHi96PI_aGg2gF-gM058mhQY1JECBVtG4ZyjKHUgDNIfhCsRQO3dmXQ/s2670/Screenshot%202024-01-31%20at%206.43.47%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1344" data-original-width="2670" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij2LhwEvpBLnVLq0HyMGUOkQRMQYxdhrGKDsE8zcT7iBgfT7gO-MyYgAgmVBzNKoMsNJxaMG8uhBi146APXOYG8r_MpWaDGknD_ERg_Iv4gz5tqIWlvs1nSyvjobfzNlya45_DsHi96PI_aGg2gF-gM058mhQY1JECBVtG4ZyjKHUgDNIfhCsRQO3dmXQ/w640-h322/Screenshot%202024-01-31%20at%206.43.47%E2%80%AFPM.png" width="640" /></a></div>As you can see above, the Total Hours now refers to the <b>{!Total}</b> resource that we created. Click on <b>Done</b>. I'm sharing the final value of display text again below for your reference:</div><pre>Hours worked on Monday = {!Monday}
Hours worked on Tuesday = {!Tuesday}
Hours worked on Wednesday = {!Wednesday}
Hours worked on Thursday = {!Thursday}
Hours worked on Friday = {!Friday}
Total Hours = {!Total}</pre><div>Let's debug our flow to see how it's working!</div><div><h3 style="text-align: left;">Flow Demo</h3>Save this flow using the <b>Save</b> button and click on the <b>Debug</b> button to debug our flow:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMvajkrOayrflIR1NewiOpBtJWdUJdu17IewKre_z9T61ReltljPS4EzZ5FExw-iaTt1pBH5ntHsGsv2_SOkYBfD5nh9ZRy8CedoB4hXYWd_BMykFy8j68574Dg9IeSib6dsWD7lvmGheugYf2Ux8lKepV-msbqiG353Au7WOttlzREZICLBTKIbnnnvE/s1728/Screenshot%202024-01-31%20at%206.51.04%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="942" data-original-width="1728" height="348" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMvajkrOayrflIR1NewiOpBtJWdUJdu17IewKre_z9T61ReltljPS4EzZ5FExw-iaTt1pBH5ntHsGsv2_SOkYBfD5nh9ZRy8CedoB4hXYWd_BMykFy8j68574Dg9IeSib6dsWD7lvmGheugYf2Ux8lKepV-msbqiG353Au7WOttlzREZICLBTKIbnnnvE/w640-h348/Screenshot%202024-01-31%20at%206.51.04%E2%80%AFPM.png" width="640" /></a></div>This is how our flow is working now:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg94FT-YasvP7B1r1xw1je4syzcmAQEYQ9apXyYKMVaFA1BWffAb1lbwWMLF3fQrHT65-KC_CbbRE8wLhZUDmmNhdHWJpWE61YQreYJUqniSZ0YvAnnJIeBKgiIK53PzXNfnuzGgZ4SJPYr2sOioqr9OocsjeR0VS7wr7R7eY8IC3XXXtAwnH1iBU1M8MU/s1440/CPT2401311855-1440x813.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="813" data-original-width="1440" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg94FT-YasvP7B1r1xw1je4syzcmAQEYQ9apXyYKMVaFA1BWffAb1lbwWMLF3fQrHT65-KC_CbbRE8wLhZUDmmNhdHWJpWE61YQreYJUqniSZ0YvAnnJIeBKgiIK53PzXNfnuzGgZ4SJPYr2sOioqr9OocsjeR0VS7wr7R7eY8IC3XXXtAwnH1iBU1M8MU/w640-h362/CPT2401311855-1440x813.gif" width="640" /></a></div><div><div>If you notice, the values we're entering are reflected in real time in our display text component and the total is also updated properly. This is how you can use Display Text as a reactive component in a screen flow.</div><h3 style="text-align: left;">Using text template to show reactive changes</h3><div>Do you know, in the latest release, <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_automate_flow_builder_use_text_templates_to_react_to_changes_on_the_same_screen.htm&release=248&type=5" target="_blank"><span style="color: #2b00fe;">Text Template resources can also respond in real time to changes in the output of a component</span></a>. Let's create a reactive text template to see how this works using an interesting example but before this, we need to create two static text templates. One will show the success message and another one will show the warning message.</div><div><br /></div><div>Let's create a success message text template i.e. a new resource with the details as provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCriEr993U-msBn6krhlQx7rDQ5Txls72UbESl22S9_LRL8aRoCx7wz2ckiygpJ3ffBw49drugZ0-ThtSNL4wiAnshqnZVorLXJbOTbBRCMgam54gpPpOL4K1v4-RgERT4znR41ZQ9mbuNb7cqACbgAzAvTpEH_UdPJ9lTN4aZB50IhMPln7S4G6JFdOc/s1796/Screenshot%202024-01-31%20at%207.07.20%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1396" data-original-width="1796" height="498" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCriEr993U-msBn6krhlQx7rDQ5Txls72UbESl22S9_LRL8aRoCx7wz2ckiygpJ3ffBw49drugZ0-ThtSNL4wiAnshqnZVorLXJbOTbBRCMgam54gpPpOL4K1v4-RgERT4znR41ZQ9mbuNb7cqACbgAzAvTpEH_UdPJ9lTN4aZB50IhMPln7S4G6JFdOc/w640-h498/Screenshot%202024-01-31%20at%207.07.20%E2%80%AFPM.png" width="640" /></a></div><b>Resource Type</b>: Text Template</div><div><b>API Name</b>: SuccessText</div><div><b>Description</b>: This message will be shown when the total time log value for the week is greater than or equal to 40</div><div><b>Body</b>: <b><span style="color: #38761d;">Your time log looks great. Thank you!</span></b><br /><div><br /><div>If you notice above, the text in the body of this text template is <b>bold</b> and is in <span style="color: #38761d;"><b>Green</b></span> color. Similarly, we can create another text template for the warning message. The details are provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm6tFiKYhzb5nZOlBVsfHU9c1VtB73C1GJKT9eY-bqV3T_dsj3zsElxSd5UjjkmkLGGa7stFIYe84btBTCwvEpPury_M3TcXuLYiI7iwJtK11l6e3HFBdXy_2L92HSl0ijFPKvE5c5zpAQyfop1AknK-D14vFyosZimoviGIMOtMYeClDfty7qE7yr_2g/s1884/Screenshot%202024-01-31%20at%207.11.49%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1346" data-original-width="1884" height="458" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm6tFiKYhzb5nZOlBVsfHU9c1VtB73C1GJKT9eY-bqV3T_dsj3zsElxSd5UjjkmkLGGa7stFIYe84btBTCwvEpPury_M3TcXuLYiI7iwJtK11l6e3HFBdXy_2L92HSl0ijFPKvE5c5zpAQyfop1AknK-D14vFyosZimoviGIMOtMYeClDfty7qE7yr_2g/w640-h458/Screenshot%202024-01-31%20at%207.11.49%E2%80%AFPM.png" width="640" /></a></div><b>Resource Type</b>: Text Template</div><div><b>API Name</b>: WarningText</div><div><b>Description</b>: This message will be shown when the total time log value for the week is less than 40</div><div><b>Body</b>: <b><span style="color: red;">Note: You've worked less hours in this week</span></b><br /><div><br /></div><div>Again, here as well, we've kept the text in the body of this text template as <b>bold</b> and it's in <b><span style="color: red;">Red</span></b> color. Click on <b>Done</b>.</div><div><br /></div><div>Now, let's create a formula resource which we'll refer in our reactive text template. The details are provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjglLCjTrx0q1k5rwtYYCrz3HAlHf4OrTcoALpno0zPPRws3g3Yx5eS9RgLhaCLpjn3piEDhZqpDi_iksR2Cc1TlRwZFO5ELMV_SlwXaIJFzntUPitrjNmwUkqQ_AAUFt83Jh1Y3Oq6iuqujGqk3gtM3zZoueoG9_vy7z1V1YTsXOdmzDd9a8oM4XUUEP4/s1784/Screenshot%202024-01-31%20at%207.31.21%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1382" data-original-width="1784" height="496" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjglLCjTrx0q1k5rwtYYCrz3HAlHf4OrTcoALpno0zPPRws3g3Yx5eS9RgLhaCLpjn3piEDhZqpDi_iksR2Cc1TlRwZFO5ELMV_SlwXaIJFzntUPitrjNmwUkqQ_AAUFt83Jh1Y3Oq6iuqujGqk3gtM3zZoueoG9_vy7z1V1YTsXOdmzDd9a8oM4XUUEP4/w640-h496/Screenshot%202024-01-31%20at%207.31.21%E2%80%AFPM.png" width="640" /></a></div><b>Resource Type</b>: Formula</div><div><b>API Name</b>: SuccessWarningText</div><div><b>Description</b>: This formula will provide the warning message if the total hours are less than 40, otherwise it'll show the success message</div><div><b>Data Type</b>: Text</div><div><b>Formula</b>: IF({!Total} < 40, {!WarningText}, {!SuccessText})<br /><div><br /></div><div>This formula will basically return the warning text template if the total hours added by the user are less than 40 for the current week. Otherwise, it'll return the success text template. Let's create another text template now, which will refer to this formula and will therefore become reactive according to the input added by the user. The details for the same are provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxHzb6wwtlRC6MsRDuH_nQW8cjKI5nh2Gh9uI-KyXMzbFFrr-dPQU0obQEuVb7SDqLYUeU3noErdmLyfGduHIAovLi75xTo9irpAX1tP6lj9TgCPGpqXTV3Qtt1BMNvrN7h14N71yg_c3EHVV8FpP0cVu3UtuViQWl9mUm1cayef6tOov0BjZ4bJuuhcw/s1782/Screenshot%202024-01-31%20at%207.35.52%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1344" data-original-width="1782" height="482" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxHzb6wwtlRC6MsRDuH_nQW8cjKI5nh2Gh9uI-KyXMzbFFrr-dPQU0obQEuVb7SDqLYUeU3noErdmLyfGduHIAovLi75xTo9irpAX1tP6lj9TgCPGpqXTV3Qtt1BMNvrN7h14N71yg_c3EHVV8FpP0cVu3UtuViQWl9mUm1cayef6tOov0BjZ4bJuuhcw/w640-h482/Screenshot%202024-01-31%20at%207.35.52%E2%80%AFPM.png" width="640" /></a></div><b>Resource Type</b>: Text Template</div><div><b>API Name</b>: ReactiveTextTemplate</div><div><b>Description</b>: Text template used in the screen</div><div><b>Body</b>: {!SuccessWarningText}<br /><div><br /></div><div>If you see, this text template is referring to our <b>SuccessWarningText</b> formula which we created before. It'll show the success or warning message on the screen according to the input added by the user. Click on <b>Done</b>.</div><div><br /></div><div>Now, it's time to edit our screen and add another <b>Display Text</b> element as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0zpSvq7gOiqQ2F7A7Kt4X1lYUeNkG9ge5Nn7QLkyUSOwTtsWQPvihcrs7kQZupmt88Q371a76kCdvmuL88xTwt1jHVi4Qay3dNLY2MIG-NamNsFdQAZfulxhf8__82kS532WqbZ5IT0NPnfw_iSpEP2xZYjZQFVadwnzubPojlqv74vCTLrVM0QJvYOc/s2666/Screenshot%202024-01-31%20at%207.39.11%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1314" data-original-width="2666" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0zpSvq7gOiqQ2F7A7Kt4X1lYUeNkG9ge5Nn7QLkyUSOwTtsWQPvihcrs7kQZupmt88Q371a76kCdvmuL88xTwt1jHVi4Qay3dNLY2MIG-NamNsFdQAZfulxhf8__82kS532WqbZ5IT0NPnfw_iSpEP2xZYjZQFVadwnzubPojlqv74vCTLrVM0QJvYOc/w640-h316/Screenshot%202024-01-31%20at%207.39.11%E2%80%AFPM.png" width="640" /></a></div>The API name for this display text is <b>ReactiveTextTemplateDisplayText</b> and it's referring to our <b>{!ReactiveTextTemplate}</b> variable as shown above. Click on <b>Done</b>. Save and debug the flow again, the updated flow is working as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmsPsLno8wlWCWliXEzdjriOZYsv5RMM3nAlLZe1IRq9ut7gySauaJi-rngCuIi-O5YovIfjiloavNYYK_-eOd4RkBPqk2BqHpXflyLZOGtS67xcvB7W0GVdBqdfFDjZzAeICUcf2fftk_Fb9yY_78MYtKsw8AoFXoVGyBd8-0GE17hbw0fUyw-r3R7Bk/s1038/CPT2401311943-1038x630.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="630" data-original-width="1038" height="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmsPsLno8wlWCWliXEzdjriOZYsv5RMM3nAlLZe1IRq9ut7gySauaJi-rngCuIi-O5YovIfjiloavNYYK_-eOd4RkBPqk2BqHpXflyLZOGtS67xcvB7W0GVdBqdfFDjZzAeICUcf2fftk_Fb9yY_78MYtKsw8AoFXoVGyBd8-0GE17hbw0fUyw-r3R7Bk/w640-h388/CPT2401311943-1038x630.gif" width="640" /></a></div>So, this is how you can also use text template as a reactive component to show real time output for the input entered by the user.</div><div><br /></div><div>That's all for this tutorial everyone, I hope you liked it. Let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-91724942083920204622024-01-22T05:30:00.000-08:002024-01-22T05:30:00.138-08:00Solution for "System.QueryException: List has no rows for assignment to SObject" - Null Coalescing Operator (??) | Spring'24 ReleaseHello Trailblazers,<div><br /></div><div>According to the <b>International Survey of Issues faced by Salesforce Developers (which was NEVER conducted) by SFDC Stop</b>: Every salesforce developer has faced the below error at least once in their developer career:</div><div><br /></div><div><b>System.QueryException: List has no rows for assignment to SObject</b></div><div><b><br /></b></div><div>This error mainly occurs when you're trying to perform a SOQL query which (ideally) should return a single record and you're trying to store that record in a variable of that sObject type which you're querying. Let's take a look at the below code as an example:</div><pre>Contact contactRecord = [SELECT Name FROM Contact WHERE Id = '003H3000001lIHsIAM'];
System.debug(contactRecord);</pre><div>If you notice above, we're querying a contact record using the id of the contact and then displaying it in the log. The result for this code snippet is shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ioBNUsYHkZ_u9mRThfSX-vXZWo5Nl0f30wz3WSspnrEA-OHVLFtN1t29Rm8ZiPBzPc_BM2-Mi4YVOerdPkHJX3b0Sg_72DBQ2OQWkk1LZPv3tRph_x_WNRYfOYOgB4Pu5UJtDDEch773W_kuNOGkF92IhE_lxipwRN-2DYOIgDqeo3e_Z34kbGvoyg0/s1876/Screenshot%202024-01-21%20at%201.39.33%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1112" data-original-width="1876" height="380" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9ioBNUsYHkZ_u9mRThfSX-vXZWo5Nl0f30wz3WSspnrEA-OHVLFtN1t29Rm8ZiPBzPc_BM2-Mi4YVOerdPkHJX3b0Sg_72DBQ2OQWkk1LZPv3tRph_x_WNRYfOYOgB4Pu5UJtDDEch773W_kuNOGkF92IhE_lxipwRN-2DYOIgDqeo3e_Z34kbGvoyg0/w640-h380/Screenshot%202024-01-21%20at%201.39.33%E2%80%AFPM.png" width="640" /></a></div>A contact with name <b>Andy Young</b> is queried and assigned to our <b>contactRecord</b> variable successfully. I checked it in our salesforce org, and the contact with the same id and name as <b>Andy Young </b>is present as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivqG1fxwXzSpbTHWMP0ien4EJYcYowy7nXF_N5ky4ZnDt_rwykSTHVCZSNmWDYJ5MZSjGO-9fHDBsJ7Csr1GGCNRfBW1sNCOoucgRmE4tdq4_1eDSwhvClO6Trqk4dyjRZrU4iSk2tPS1VQg3c8P7V5TG2D9TN7Krfj9LL2qSiqStRY9VYuOJbJ3J5zL8/s1938/Screenshot%202024-01-21%20at%201.43.08%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1266" data-original-width="1938" height="418" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivqG1fxwXzSpbTHWMP0ien4EJYcYowy7nXF_N5ky4ZnDt_rwykSTHVCZSNmWDYJ5MZSjGO-9fHDBsJ7Csr1GGCNRfBW1sNCOoucgRmE4tdq4_1eDSwhvClO6Trqk4dyjRZrU4iSk2tPS1VQg3c8P7V5TG2D9TN7Krfj9LL2qSiqStRY9VYuOJbJ3J5zL8/w640-h418/Screenshot%202024-01-21%20at%201.43.08%E2%80%AFPM.png" width="640" /></a></div>Till now, everything is working as expected. Now the question is: <b>What will happen when we delete this contact record and try to execute the same code again?</b><div><br /></div><div>Let's give this a try! I deleted this contact record from our salesforce org and now we are getting the below error when we try to open the detail page of this contact:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCmA8tfXlvQvPm2QvXjkmZl0kAZjcUOYlF23QUwRAw_-BiexmBisZdU7kggeuzSBRbknh7GjD3sG9osevMrVkbIgZCxH6PoPc24ea2-N1AvaI_PxLLbViDsV_9w00Kp_i071A7IdoA-IF2dd0tjW23QrxP4TAnVEKt6XWSsX7MCljtkyr8XDwtCTRzB-g/s2254/Screenshot%202024-01-21%20at%201.48.31%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="742" data-original-width="2254" height="210" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCmA8tfXlvQvPm2QvXjkmZl0kAZjcUOYlF23QUwRAw_-BiexmBisZdU7kggeuzSBRbknh7GjD3sG9osevMrVkbIgZCxH6PoPc24ea2-N1AvaI_PxLLbViDsV_9w00Kp_i071A7IdoA-IF2dd0tjW23QrxP4TAnVEKt6XWSsX7MCljtkyr8XDwtCTRzB-g/w640-h210/Screenshot%202024-01-21%20at%201.48.31%E2%80%AFPM.png" width="640" /></a></div><b>The requested resource</b> (our contact record)<b> does not exist</b> (in our salesforce org), because we deleted this record. I executed the same code snippet to query this contact record and this time, we're getting the error <b>"System.QueryException: List has no rows for assignment to SObject"</b> as you can see below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitufJq463WH5G2wdgcnQPMWdLsHFULC5tDDbRmevifbq9eI9C0PmnONoXKSLh017b3jp7LDV64sojgiul-HHFXziGaVgC3GlS-gzBvhc19jot4Dl8qQejUzpkP2wYT3RiREhbCkasN9OjSiDNiSQNyNnyfYfele14sTg4_Sg0NjfPiQN4H9eDd2Waw9GY/s1846/Screenshot%202024-01-21%20at%201.51.00%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1128" data-original-width="1846" height="392" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitufJq463WH5G2wdgcnQPMWdLsHFULC5tDDbRmevifbq9eI9C0PmnONoXKSLh017b3jp7LDV64sojgiul-HHFXziGaVgC3GlS-gzBvhc19jot4Dl8qQejUzpkP2wYT3RiREhbCkasN9OjSiDNiSQNyNnyfYfele14sTg4_Sg0NjfPiQN4H9eDd2Waw9GY/w640-h392/Screenshot%202024-01-21%20at%201.51.00%E2%80%AFPM.png" width="640" /></a></div>This error is coming because our contact record with id <b>003H3000001lIHsIAM</b> doesn't exist in our salesforce org now. <b>The query which we're performing is returning a list which has no rows (no records) to assign to the contactRecord variable which is of type Contact sObject</b>. So, we can say "List has no rows for assignment to SObject".<div><br /></div><div>In order to solve this error, we can use the <b>Null Coalescing Operator (??)</b> which is delivered by salesforce in Spring 24 release. This operator is denoted by two question marks: <b>??</b> and is a binary operator of form: <b>x ?? y</b>. It basically says that: <b>If</b> <b>x is not null, it'll return x. Otherwise, it'll return y</b>. Now, for our scenario, the list of contact records we're trying to query is coming as <b>empty</b> which is why we cannot refer to the first element of that list in order to assign it to contactRecord variable. We can modify our code to assign a default contact record when the list is coming as null. Let's see the modified code below:</div><pre style="white-space: pre-wrap;">Contact contactRecord = [SELECT Id FROM Contact WHERE Id = '003H3000001lIHsIAM'] ?? new Contact(LastName='Malhotra');
System.debug(contactRecord);</pre><div>If you see above, at the end of the query, we've added: <b>?? new Contact(LastName='Malhotra');</b></div><div><div><div><br /></div><div>We're using the Null Coalescing Operator <b>??</b> at the end of our contact query, keeping a new (default) instance of contact object where LastName = Malhotra. Let's see what happens when we try to execute this code:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbxeM5DpjIJmwxY1j1Xwk_-tZNaSTkyhuiagsKYKoUYqkw2uMzE0N0K42vXvHwBRNPzRjGLNLYwSWHDA-GISlrxah4EUWJXw9BN9HPeNadmAKKP0BC8I_avwuVN3Lx7ESKDWOEbPv1XPyKDm7gMt52XdkKYgtB4Ax1GtUNgqxIhXfCEm4vTkNZutpoYes/s2560/Screenshot%202024-01-21%20at%202.04.13%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1130" data-original-width="2560" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbxeM5DpjIJmwxY1j1Xwk_-tZNaSTkyhuiagsKYKoUYqkw2uMzE0N0K42vXvHwBRNPzRjGLNLYwSWHDA-GISlrxah4EUWJXw9BN9HPeNadmAKKP0BC8I_avwuVN3Lx7ESKDWOEbPv1XPyKDm7gMt52XdkKYgtB4Ax1GtUNgqxIhXfCEm4vTkNZutpoYes/w640-h282/Screenshot%202024-01-21%20at%202.04.13%E2%80%AFPM.png" width="640" /></a></div>As you can see above, instead of throwing error, our debug is now showing the default contact record with last name as <b>Malhotra</b>. This means that: the instance of our contact record which is after the Null Coalescing Operator is assigned to our <b>contactRecord</b> variable. This is because the list of contact records which were queried was coming out to be null.<br /><div><br /></div><div>Therefore, using our new <b>Null Coalescing Operator</b>, we're able to deal with the scenario when our query doesn't return any rows so that we don't get any error.</div></div></div><h3 style="text-align: left;">Points to remember about Null Coalescing Operator</h3><div><ul style="text-align: left;"><li>Operator precedence will apply as is while using Null Coalescing Operator. For example: <b>a ?? 25 + 50 ?? b</b> will be evaluated as: <b>a ?? (25 + 50 ?? b)</b> and not <b>(a ?? 25) + (50 ?? b)</b>. We should use appropriate paranthesis to make sure the evaluation is done correctly. Let's take a look at the result of mentioned examples below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFwoMoS4s7WK0W71sQjMfDIqU_1B9Fd8fDxIPCVrzhbiEUfmJK9DUJdIqiOTPuGo4RKttJzD01tqszIYBFU70day7Em-NasOt5p8X0TSydq17vYVJFjG6Kh_ERfrCpJC7WsnZjHI_2MGz4WiF7MUmHbHfHKdjvfuy84VdHVqyIuUZVhdmiVIULSf3gghM/s1350/Screenshot%202024-01-22%20at%201.29.33%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="984" data-original-width="1350" height="466" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFwoMoS4s7WK0W71sQjMfDIqU_1B9Fd8fDxIPCVrzhbiEUfmJK9DUJdIqiOTPuGo4RKttJzD01tqszIYBFU70day7Em-NasOt5p8X0TSydq17vYVJFjG6Kh_ERfrCpJC7WsnZjHI_2MGz4WiF7MUmHbHfHKdjvfuy84VdHVqyIuUZVhdmiVIULSf3gghM/w640-h466/Screenshot%202024-01-22%20at%201.29.33%E2%80%AFPM.png" width="640" /></a></div>As we executed the above code snippet, you can see that the result for <b>a ?? 25 + 50 ?? b</b> is coming out to be <b>10</b> as <b>a</b> has the value <b>10</b>. However, the result for <b>(a ?? 25) + (50 ?? b)</b> is coming out to be <b>60</b> as the evaluation for this will be <b>10 + 50 = 60</b>.<br /><br /></li><li>You cannot use Null Coalescing operator as the left side of the assignment. For example: <b>a ?? b = 50;</b> doesn't evaluate to anything as shown below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFcVULOvsEuJIac_rEnPWek1sIAu3Qj36G5WzhrDqvMPKo6cdJ2GPZUF1ELbufSYMy0m3KTqNmlZVLnGbi7Prns4RIoD8s_7vPCOLSnc5FNbfraGKFQFX3eTfnzcpcE4y8b9vZ2p9JEybvqWhfkGyGIGSKpPzitj0eB2I91mYFKBmK_aD8_mNfNntc6l4/s896/Screenshot%202024-01-22%20at%202.40.21%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="606" data-original-width="896" height="432" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFcVULOvsEuJIac_rEnPWek1sIAu3Qj36G5WzhrDqvMPKo6cdJ2GPZUF1ELbufSYMy0m3KTqNmlZVLnGbi7Prns4RIoD8s_7vPCOLSnc5FNbfraGKFQFX3eTfnzcpcE4y8b9vZ2p9JEybvqWhfkGyGIGSKpPzitj0eB2I91mYFKBmK_aD8_mNfNntc6l4/w640-h432/Screenshot%202024-01-22%20at%202.40.21%E2%80%AFPM.png" width="640" /></a></div></li></ul><div>That's all for this tutorial. I hope you liked it, let me know your feedback in the comments down below.</div></div><div><br /></div><div><b>Happy Trailblazing!!</b></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-83840355536640121382024-01-15T05:30:00.000-08:002024-01-15T05:30:00.139-08:00Embed screen flow in LWC component: Pass data to screen flow and Receive data from Screen Flow<p>Hello Trailblazers,</p><p><br /></p><p>You might have come across the below blog posts published by me in the past:</p><p></p><ol style="text-align: left;"><li><a href="https://www.sfdcstop.com/2022/10/how-to-pass-data-from-lwc-to-screen.html" target="_blank"><span style="color: #2b00fe;">How to pass data from lwc to screen flow in salesforce?</span></a><br /></li><li><a href="https://www.sfdcstop.com/2022/10/how-to-pass-data-from-screen-flow-to.html" target="_blank"><span style="color: #2b00fe;">How to pass data from screen flow to lwc in salesforce?</span></a></li></ol><p></p><p><br /></p><p><b>You might be thinking, what the heck are we doing in this post then???</b></p><p><b><br /></b></p><p><b>Give me a moment to clarify:</b> In the above posts, we actually embedded a LWC component within a screen flow and passed data to it/received data from it. However, in today's post, we're going to do exactly the opposite. We're going to embed a screen flow within a LWC component and pass data to screen flow, receive data from screen flow. We're going to use <b>lightning-flow</b> lwc component provided by salesforce in this tutorial. So, without spending much time on discussion. Let's begin!</p><p><br /></p><p>First of all we're going to create a very basic LWC component called <b>flowContainer</b> which will have our flow. We're going to embed this component on the homepage just like other demo components which we created. The content of .meta-xml file for the same is provided below:</p><h3 style="text-align: left;">flowContainer.js-meta.xml</h3><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Flow Container</masterLabel>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre><h3 style="text-align: left;">Screen Flow - Duplicate Contacts</h3><div>Before jumping on to more LWC code, let's create our screen flow first. This flow, named as <b>Duplicate Contacts</b>, is going to do the following:</div><div><br /></div><div><ol style="text-align: left;"><li>Get the list of contact ids from flowContainer LWC</li><li>Create a new account record</li><li>Query the existing contact records using their ids, create a copy of those, tag them to the new account record and insert them in salesforce</li><li>Pass the new account record id to flowContainer LWC</li></ol></div><div><br /></div><div>Follow the below steps to create our screen flow:</div><div><br /></div><div>1. Go to setup. Search for flow and click the <b>New Flow</b> button</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXsl0YCi1eLEuyR0sErxkSJ_DntR8ZZQ8ga8b4Wg-2UQhgTTAuryW3FvQafBkyr0VeFz3eFQBW5_Sq3DGa5whGBIg6vbLyar6N1gI6YcjHM0pIcbIRJifLoWA39TySDx8hdcNGk3H0yQ83f4lAStlYGspgeJLQWPAwHvXB8quAhxWKGHkVxSOvXwteLSQ/s2880/Screenshot%202024-01-14%20at%206.10.26%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="736" data-original-width="2880" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXsl0YCi1eLEuyR0sErxkSJ_DntR8ZZQ8ga8b4Wg-2UQhgTTAuryW3FvQafBkyr0VeFz3eFQBW5_Sq3DGa5whGBIg6vbLyar6N1gI6YcjHM0pIcbIRJifLoWA39TySDx8hdcNGk3H0yQ83f4lAStlYGspgeJLQWPAwHvXB8quAhxWKGHkVxSOvXwteLSQ/w640-h164/Screenshot%202024-01-14%20at%206.10.26%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div>2. In the new flow screen, choose <b>Screen Flow</b> option and click <b>Create</b><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzX2B1oA7_t9TwBqCGt3F_RF05nSwyJQd3nLPOQ3UcPuoiibykRuTitnhHcnHiX2Ecnf3EKpUn5Qzs2SzVq9OOgFu99JaiO6ON7tRDL6fwfM2QdwrTwIQqSWxuAfnlUMjeKB8B1ojaIhzTjJhu3M_r2NM6iJ8Z6CMMjiCUguy-230LlcSejmxuz5Xg24A/s2880/Screenshot%202024-01-14%20at%206.13.05%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1398" data-original-width="2880" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzX2B1oA7_t9TwBqCGt3F_RF05nSwyJQd3nLPOQ3UcPuoiibykRuTitnhHcnHiX2Ecnf3EKpUn5Qzs2SzVq9OOgFu99JaiO6ON7tRDL6fwfM2QdwrTwIQqSWxuAfnlUMjeKB8B1ojaIhzTjJhu3M_r2NM6iJ8Z6CMMjiCUguy-230LlcSejmxuz5Xg24A/w640-h310/Screenshot%202024-01-14%20at%206.13.05%E2%80%AFPM.png" width="640" /></a></div><br /><div>3. In the <b>Toolbox</b> on the left hand side, click <b>New Resource</b>. The inputs are provided below:</div><div> Resource Type: <b>Variable</b><br /></div><div><span> API Name: <b>ids</b></span></div><div><span><span style="font-weight: bold;"> </span><b> </b>Description: </span><b>List of contact ids</b></div><div><span style="font-weight: bold;"> </span><b> </b>Data Type: <b>Text</b><br /></div><div><span style="font-weight: bold;"> </span><b> </b>Availability Outside the Flow: <b>Available for input</b><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIO4FdfQncOx8rNam5Sdw-QYCHQl5nK4I6jMUwN9v4m2Q3Anjd9T7XgJ171q_NfaYQA7wj5ICVsvgmIzga7DsupxW0GB7r3kJ4mTnGDw1CtfvDDk2dAjhIvY9ypZvOEqq-iS4oL14K43SWqNJ9M5dr7IVxy_VoGvYAVP0-gjnSOki7BR4DoGWmGbfEqoE/s2880/Screenshot%202024-01-14%20at%206.16.04%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1480" data-original-width="2880" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIO4FdfQncOx8rNam5Sdw-QYCHQl5nK4I6jMUwN9v4m2Q3Anjd9T7XgJ171q_NfaYQA7wj5ICVsvgmIzga7DsupxW0GB7r3kJ4mTnGDw1CtfvDDk2dAjhIvY9ypZvOEqq-iS4oL14K43SWqNJ9M5dr7IVxy_VoGvYAVP0-gjnSOki7BR4DoGWmGbfEqoE/w640-h328/Screenshot%202024-01-14%20at%206.16.04%E2%80%AFPM.png" width="640" /></a></div><br /><div>We basically created a collection variable <b>ids</b> here, which will receive the list of contact ids from our flowContainer LWC component. We checked <b>Available for input</b> as we're going to receive it's value from outside the flow i.e. from our LWC component.</div><div><br /></div><div><b>Note: </b>No need to check these boxes otherwise. I've seen a lot of developers checking both <b>Available for input</b> and <b>Available for output</b> boxes without any reason. Please see if you really need an input in this variable from somewhere outside the flow and then only check this checkbox.</div><div><br /></div><div>Before moving ahead, let's save the flow using the <b>Save</b> button on the top right. Click the <b>Save</b> button, fill in the details as shown below and click the save button present on the popup again to save the flow.</div><div><span> </span><span> </span>Flow Label: <b>Duplicate Contacts</b></div><div><span> </span><span> </span>Flow API Name: <b>DuplicateContacts</b></div><div><span> </span><span> </span>Description: <b>This flow will duplicate existing contact records based on ids and link them with a new account record</b></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG6UfAhoL-h70BSO4iL1Mk74z5cqIBk9XVdYAYmh4rEnUVVWLJWH5h6KTeha3S3FPEgvBTItsdPEN6LmRX1b00I5KGTuNB508PVKYxBymuLEg7RB0yfBuewJU5wscdzKm0xZMeyYFW_NlaHPxpsqGzFmMt8KZAWwPaMdLCc-wpqkLH7ijY7RxzCAPoO5I/s2880/Screenshot%202024-01-14%20at%206.24.45%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1246" data-original-width="2880" height="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG6UfAhoL-h70BSO4iL1Mk74z5cqIBk9XVdYAYmh4rEnUVVWLJWH5h6KTeha3S3FPEgvBTItsdPEN6LmRX1b00I5KGTuNB508PVKYxBymuLEg7RB0yfBuewJU5wscdzKm0xZMeyYFW_NlaHPxpsqGzFmMt8KZAWwPaMdLCc-wpqkLH7ijY7RxzCAPoO5I/w640-h276/Screenshot%202024-01-14%20at%206.24.45%E2%80%AFPM.png" width="640" /></a></div><div><br /></div>Inside this flow, we want to have a screen first which will create a new Account record. But before that, we need a resource of type <b>Variable</b> and object as <b>Account</b>. The details of the new resource is provided below:<div><div class="separator" style="clear: both;"> Resource Type: <b>Variable</b><br /></div><div class="separator" style="clear: both;"><span style="font-weight: bold;"> </span><b> </b>API Name: <b>NewAccount</b><br /></div><div class="separator" style="clear: both;"><span style="font-weight: bold;"> </span><b> </b>Description: <b>This variable will store the new account record which is created</b></div><div class="separator" style="clear: both;"><span style="font-weight: bold;"> </span><b> </b>Data Type: <b>Record</b><br /></div><div class="separator" style="clear: both;"><span style="font-weight: bold;"> </span><b> </b>Object: <b>Account</b><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxc1i8H9kDm9hUOdtWTK7Q5UUOpu33CoaNtcoKXx8AuZMrcNHKVNmcFQaqTk8rv7DbTr5X3jKswljMZwrdosei9xXPL1GnR_1LaH3fFlcelH4v2QT_zHEjO4VjoaeB6FQAxQB2hhmNzJ4otlDgK7g1gDizJ_5WKBpn4pnyo1obNxIBNd5k9Dlw935wvRU/s2880/Screenshot%202024-01-15%20at%2011.24.49%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1432" data-original-width="2880" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxc1i8H9kDm9hUOdtWTK7Q5UUOpu33CoaNtcoKXx8AuZMrcNHKVNmcFQaqTk8rv7DbTr5X3jKswljMZwrdosei9xXPL1GnR_1LaH3fFlcelH4v2QT_zHEjO4VjoaeB6FQAxQB2hhmNzJ4otlDgK7g1gDizJ_5WKBpn4pnyo1obNxIBNd5k9Dlw935wvRU/w640-h318/Screenshot%202024-01-15%20at%2011.24.49%E2%80%AFAM.png" width="640" /></a></div><div><div><br /></div><div>The screen to create a new account is provided below:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzczlwqGQxdSUcPQaDS8j22cGT8xdFJZcbVU9rFYe1UVUffEZhbz3dG7CGDE_is1y628hhO_PcmEiQZYqraABz8X2sOTiO7cHHc0TtW6Hdq3XkgLuyz6F0yLkVOTMRQqf9Oy3BEBkJi74RlVyCNJYweT0lmdKSOBK9HfohemEKHxWAcME0rjGXy-B0E20/s2668/Screenshot%202024-01-15%20at%2011.17.41%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1280" data-original-width="2668" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzczlwqGQxdSUcPQaDS8j22cGT8xdFJZcbVU9rFYe1UVUffEZhbz3dG7CGDE_is1y628hhO_PcmEiQZYqraABz8X2sOTiO7cHHc0TtW6Hdq3XkgLuyz6F0yLkVOTMRQqf9Oy3BEBkJi74RlVyCNJYweT0lmdKSOBK9HfohemEKHxWAcME0rjGXy-B0E20/w640-h308/Screenshot%202024-01-15%20at%2011.17.41%E2%80%AFAM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">The header is: <b>Enter Account Details</b></div><div class="separator" style="clear: both; text-align: left;">We added a single textbox in this screen which will store the Account Name as provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi535ieWZMM7sxVhyphenhyphenWjzSDVBCl0m4aWQWLIlyfQav2jIRo52-kVxtMI-prjJR28c7MfraUd3H2-wTQA3j-xide8eu3lU8uXrEmY09QwlVH7cvBCUJZDq2fDwBN5tCuCrTQcX9pYX52dr5GqTjeLhp4U-9P5Vzzvd8EwT-0RIHqwjYOAp2TqdUNSI3cOS78/s2880/Screenshot%202024-01-15%20at%2011.43.27%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1476" data-original-width="2880" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi535ieWZMM7sxVhyphenhyphenWjzSDVBCl0m4aWQWLIlyfQav2jIRo52-kVxtMI-prjJR28c7MfraUd3H2-wTQA3j-xide8eu3lU8uXrEmY09QwlVH7cvBCUJZDq2fDwBN5tCuCrTQcX9pYX52dr5GqTjeLhp4U-9P5Vzzvd8EwT-0RIHqwjYOAp2TqdUNSI3cOS78/w640-h328/Screenshot%202024-01-15%20at%2011.43.27%E2%80%AFAM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Note that we switched to the <b>Fields</b> tab, populated the <b>RecordVariable</b> with our <b>NewAccount</b> variable that we created before and then we dragged + dropped the <b>Account Name</b> field to the screen. This will automatically bind the value entered by the user to the Name field of our NewAccount variable.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Now, we can add the <b>Create Records</b> element to our flow in order to insert this new account record. The details of <b>Create Records</b> element are provided below:</div><div><span> </span><span> Label: </span><b>Create Account</b></div><div><span> </span><span> API Name: </span><b>Create_Account</b></div><div><span> </span><span> Description: </span><b>Insert the account record present in NewAccount variable in salesforce</b></div><div> How Many Records to Create: <b>One</b></div><div><span style="font-weight: bold;"> </span><b> </b>How to Set the Record Fields: <b>Use all values from a record</b><br /></div><div><span style="font-weight: bold;"> </span><b> </b>Record: <b>NewAccount</b><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5Aet2EFsxb0bXpjKtgudZsoJsfkP6yI6f0n_ruWr1q3J69QVOH9A54BH8jaW8gOGwsc9oYUYAeaPLWBREdMyeY2ieygU5RdZjxgiHeUMd73YQdDjiOUos3EadnCL2BHJom8MFajVrMT85eZOvX_XmTn7tHZ6YWZNyd4Pb5yn_sqcno3NoZeDyD34_qno/s2880/Screenshot%202024-01-15%20at%2011.32.25%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1322" data-original-width="2880" height="294" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5Aet2EFsxb0bXpjKtgudZsoJsfkP6yI6f0n_ruWr1q3J69QVOH9A54BH8jaW8gOGwsc9oYUYAeaPLWBREdMyeY2ieygU5RdZjxgiHeUMd73YQdDjiOUos3EadnCL2BHJom8MFajVrMT85eZOvX_XmTn7tHZ6YWZNyd4Pb5yn_sqcno3NoZeDyD34_qno/w640-h294/Screenshot%202024-01-15%20at%2011.32.25%E2%80%AFAM.png" width="640" /></a></div>Once our account record is created, we need to duplicate contact records and attach them to this new account record. In order to do it, we're going to use our <b>Get Records</b> element to query the contact records using ids. The details are provided below:</div><div> Label: <b>Query Contacts</b></div><div><span> </span><span> API Name: </span><b>Query_Contacts</b></div><div><span> </span><span> Description: </span><b>Query contact records based on record ids passed to the flow</b></div><div> Object: <b>Contact</b></div><div> Filter: <b>Id</b> <b>In</b> <b>{!ids}</b></div><div><div> Sort Order: <b>Not Sorted</b></div><div> How Many Records to Store: <b>All records</b></div><div> How to Store Record Data: <b>Automatically store all fields</b></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFeIbjfRxOYLdam0_SBAZUNXQlfLKl-9ASVSkOnjpLnjrEEJyzyPcUIxcHfiaXdtNB_Mn1We8x0L3RtvyCkt-LJITJ05AQl6f0bTNhQOYCVbhuIbjqifKTXpXqTosoZwlgD14o60gZx9cDbvbp2cTCYxsbKaVm9G-sJ_izV6D1EjXljtFK0mZ6KPmbros/s2880/Screenshot%202024-01-15%20at%2011.50.30%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1626" data-original-width="2880" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFeIbjfRxOYLdam0_SBAZUNXQlfLKl-9ASVSkOnjpLnjrEEJyzyPcUIxcHfiaXdtNB_Mn1We8x0L3RtvyCkt-LJITJ05AQl6f0bTNhQOYCVbhuIbjqifKTXpXqTosoZwlgD14o60gZx9cDbvbp2cTCYxsbKaVm9G-sJ_izV6D1EjXljtFK0mZ6KPmbros/w640-h362/Screenshot%202024-01-15%20at%2011.50.30%E2%80%AFAM.png" width="640" /></a></div>If you notice above, we're using the <b>{!ids}</b> variable here which will have the ids of our existing contact records to query them. Now, we're going to remove the Id from these contact records to create new records, tag them to the newly created account and store them in a list.<br /><div><br /></div><div>In order to do that, we need a Loop element using which we'll loop all the queried contacts. The details of the same are provided below:</div><div><span> </span><span> Label: </span><b>Iterate Contacts</b></div><div><span> </span><span> API Name: </span><b>Iterate_Contacts</b></div><div><span style="font-weight: bold;"> </span><b> </b>Description: <b>Iterate the queried contacts</b></div><div><span style="font-weight: bold;"> </span><b> </b>Collection Variable: <b>{!Query_Contacts}</b></div><div><span style="font-weight: bold;"> </span><b> </b>Direction: <b>First item to last item</b><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyH6XNm39vm7T4f0hKmYmCPKZNoUnrUbcg7QlDLz0g2NWFWgWHr54vNbRKNS3MftQe9pREvjF-OarRt7z_P0UFjQ2wSp5cYv4vQd79rLikh-cMJ1tdEPoSe8U1sdHUvzn6o5aJ34JTD7Tmjr9cTroOnvj0zR3r5h_nmJyEjb4iFjptQOvztwGNUD7zhqI/s2880/Screenshot%202024-01-15%20at%2011.57.04%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1518" data-original-width="2880" height="338" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyH6XNm39vm7T4f0hKmYmCPKZNoUnrUbcg7QlDLz0g2NWFWgWHr54vNbRKNS3MftQe9pREvjF-OarRt7z_P0UFjQ2wSp5cYv4vQd79rLikh-cMJ1tdEPoSe8U1sdHUvzn6o5aJ34JTD7Tmjr9cTroOnvj0zR3r5h_nmJyEjb4iFjptQOvztwGNUD7zhqI/w640-h338/Screenshot%202024-01-15%20at%2011.57.04%E2%80%AFAM.png" width="640" /></a></div><div><br /></div>Let's create a new list first to store our modified contact records which we're going to insert. The details are provided below:</div><div><span> </span><span> </span>Resource Type: <b>Variable</b></div><div><span> </span><span> </span>API Name: <b>contactsList</b></div><div><span> </span><span> </span>Description: <b>List of contact records</b></div><div><span> </span><span> </span>Data Type: <b>Record</b></div><div><span> </span><span> </span>Object: <b>Contact</b></div><div><span> </span><span> </span>Allow multiple values (collection): <b>True</b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnd4s0sI0BVB3Ox21bXCmIKHPm9scFzKwk0KmTNrtpgAdcNW84C-gHoBaiTim4tJXVIrM7uNOZat3_MybuhBTdqvIA5xqMYzvUxOUd3g90r98zwmbC6Px4nkSY9WPui8empfdMfBX82wbt7OlC4tUJxRCXgmqNYWhd3G4JTiGT4-oDj0iUmhah4Slv9bU/s2880/Screenshot%202024-01-15%20at%201.12.23%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1444" data-original-width="2880" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnd4s0sI0BVB3Ox21bXCmIKHPm9scFzKwk0KmTNrtpgAdcNW84C-gHoBaiTim4tJXVIrM7uNOZat3_MybuhBTdqvIA5xqMYzvUxOUd3g90r98zwmbC6Px4nkSY9WPui8empfdMfBX82wbt7OlC4tUJxRCXgmqNYWhd3G4JTiGT4-oDj0iUmhah4Slv9bU/w640-h320/Screenshot%202024-01-15%20at%201.12.23%E2%80%AFPM.png" width="640" /></a></div><div><br /></div>Now, inside this loop, we'll set the <b>Id</b> and <b>AccountId</b> for every contact using an assignment element and add that contact record to a new list of contacts: <b>contactsList</b> which we created before.</div><div><br /></div><div>The details for the same are as follows:</div><div><span> </span><span> Label: </span><b>Set Id and AccountId</b></div><div><span> </span><span> API Name: </span><b>Set_Id_and_AccountId</b></div><div><span> </span><span> Description: </span><b>Set Id as empty and AccountId using the Id of newly created account for the current contact record</b></div><div><span style="font-weight: bold;"> </span><b> </b>Variable values:<br /></div><div><span> </span><span> </span><b>{!Iterate_Contacts.Id} <Equals> <Blank></b></div><div><b><span> </span><span> </span>{!Iterate_Contacts.AccountId} <Equals> {!NewAccount.Id}</b></div><div><b><span> </span><span> </span>{!contactsList} <Add> {!Iterate_Contacts}</b></div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvP4hJdsUmzp8uQU54YGlv73xKSVOgsJmNy7PMSTYJXFBGpt1afsE6i3uq6SD0vegAr1jQN7QeukpW9XuwpBG8ebvOwhx4j4XrrHS3XYDVZT8i1xlBgBpql53ks_nXklE0ugK7OgtnJz_UXR3o5r6FkCryhjr65baJhLP9cQK3UkmxX2wO6dDbpyVcOJM/s2880/Screenshot%202024-01-15%20at%201.16.45%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="1438" data-original-width="2880" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvP4hJdsUmzp8uQU54YGlv73xKSVOgsJmNy7PMSTYJXFBGpt1afsE6i3uq6SD0vegAr1jQN7QeukpW9XuwpBG8ebvOwhx4j4XrrHS3XYDVZT8i1xlBgBpql53ks_nXklE0ugK7OgtnJz_UXR3o5r6FkCryhjr65baJhLP9cQK3UkmxX2wO6dDbpyVcOJM/w640-h320/Screenshot%202024-01-15%20at%201.16.45%E2%80%AFPM.png" width="640" /></a><div><div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">After this screen we'll use the <b>Create Records</b> element to insert <b>contactsList</b> in salesforce. The details for the same are provided below:</div><div class="separator" style="clear: both; text-align: left;"><span> </span><span> </span>Label: <b>Create Contacts</b></div><div class="separator" style="clear: both; text-align: left;"><span> </span><span> </span>API Name: <b>Create_Contacts</b></div><div class="separator" style="clear: both; text-align: left;"><span> </span><span> </span>Description: <b>Insert the list of newly created contact records in salesforce</b></div><div class="separator" style="clear: both; text-align: left;"><span> </span><span> </span>How Many Records to Create: <b>Multiple</b></div><div class="separator" style="clear: both; text-align: left;"><span> </span><span> </span>Record Collection: <b>contactsList</b></div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAlukldIPDt0ZMmB2Bb_MXmeEwkNPWRRlDAEGYiBQBkBlwgUdKe6YXqIf_o9oU-XQJe-xLh1cEO9n64hVLMHjOALAkMQQS7KBf8njV5NeoL6KShvaLBYGLcaW9LK_EwcbSskexOvaflni_65XcPm6CphPsD-HY6b5B4BCC8qPE1s40iDnB8pwCSghX1WA/s2880/Screenshot%202024-01-15%20at%201.24.06%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="1628" data-original-width="2880" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAlukldIPDt0ZMmB2Bb_MXmeEwkNPWRRlDAEGYiBQBkBlwgUdKe6YXqIf_o9oU-XQJe-xLh1cEO9n64hVLMHjOALAkMQQS7KBf8njV5NeoL6KShvaLBYGLcaW9LK_EwcbSskexOvaflni_65XcPm6CphPsD-HY6b5B4BCC8qPE1s40iDnB8pwCSghX1WA/w640-h362/Screenshot%202024-01-15%20at%201.24.06%E2%80%AFPM.png" width="640" /></a><br /><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">One thing that we should update here is our <b>NewAccount</b> variable. We want to redirect the user to the newly created account record from our <b>flowContainer</b> LWC component. That means, we need to pass the new account from flow to LWC. Therefore, we can set <b>Availability Outside the Flow</b> as <b>Available for output</b> as shown below:</div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs3CEY_dljXh_ezEDwgyR-huv7-XJYHCFTcBbseEZ4OOa4vrRPpDECSOcB52Zp96HfaBTwI-XR3RkMrZ77CP9v2IWQaCwZopomjDPkjPelNcPhY4tjlmqF3jQ2cxb2QtIjaSp3LlnE1tNYWOWUpp7rIyoRYm_7y5SW03Tw3bYIzptQZFltt3v4hXEE9ko/s2880/Screenshot%202024-01-15%20at%201.32.42%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="1312" data-original-width="2880" height="292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs3CEY_dljXh_ezEDwgyR-huv7-XJYHCFTcBbseEZ4OOa4vrRPpDECSOcB52Zp96HfaBTwI-XR3RkMrZ77CP9v2IWQaCwZopomjDPkjPelNcPhY4tjlmqF3jQ2cxb2QtIjaSp3LlnE1tNYWOWUpp7rIyoRYm_7y5SW03Tw3bYIzptQZFltt3v4hXEE9ko/w640-h292/Screenshot%202024-01-15%20at%201.32.42%E2%80%AFPM.png" width="640" /></a><br /><div>Now, our flow is complete. Make sure to <b>Activate the flow</b>. It's time to move on to the html code for our LWC component.</div><h3 style="text-align: left;">flowContainer.html</h3><div>This component will basically show a button, which will launch our screen flow. Let's see the code:</div><pre><template>
<template lwc:if={showFlow}>
<lightning-flow
flow-api-name="DuplicateContacts"
flow-input-variables={inputVariables}
onstatuschange={handleStatusChange}>
</lightning-flow>
</template>
<template lwc:else>
<lightning-button label="Duplicate Contacts" onclick={launchFlow}></lightning-button>
</template>
</template></pre><div>As you can see above, we have two templates rendered on the basis of a boolean <b>showFlow</b> which is used in <b>lwc:if</b> attribute added in a template tag. If <b>showFlow</b> is true, we're displaying the <b>DuplicateContacts</b> screen flow in our lwc component using <b>lightning-flow</b> tag. If you notice inside <b>lightning-flow</b> tag, we've specified value for 3 attributes:<br /><br /></div><div><ol style="text-align: left;"><li><b>flow-api-name</b>: This should be the API name of the flow. For our flow, it's <b>DuplicateContacts</b>.</li><li><b>flow-input-variables</b>: This refers to the array of input variables i.e. the flow variables whose values we're going to pass from this LWC component.</li><li><b>onstatuschange</b>: We're capturing the <b>statuschange</b> event here and calling our <b>handleStatusChange</b> method. <b>statuschange</b> event is fired whenever the status of the flow is changed. For example: when the flow is started/paused/finished etc.</li></ol><div><br /></div><div>We're going to define <b>showFlow</b>, <b>inputVariables</b> - variables and <b>handleStatusChange</b> method in our js file. In the <b>lwc:else</b> section, we have a <b>lightning-button</b> whose label is <b>Duplicate Contacts</b> and on clicking of that button we're calling our <b>launchFlow</b> method which we're going to define in our js file which will launch our flow. So, let's move on to our js file to complete this component.</div></div></div></div></div></div></div></div><h3 style="text-align: left;">flowContainer.js</h3><div><b>This is the most important part of the tutorial as this is where we're going to pass data from LWC to screen flow and we're going to receive data from screen flow in our LWC component.</b> Let's take a look at the code below:</div><pre style="white-space: pre-wrap;">import { LightningElement } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';
export default class FlowContainer extends NavigationMixin(LightningElement) {
// * Boolean to display/hide flow
showFlow = false;
// * Ids of contact records to be passed to flow
contactIds = [
'003H3000001l11BIAQ',
'003H3000001l11AIAQ',
'003H3000001l112IAA'
];
// * Input variables to pass to flow from LWC
inputVariables = [
{
name: 'ids',
type: 'String',
value: this.contactIds
}
]
/**
* @description This method is used to launch the flow from lwc
*/
launchFlow() {
this.showFlow = true;
}
/**
*
* @param {object} event status change event - received when flow state is changed
* @description This method will be called whenever the state of the flow is updated
*/
handleStatusChange(event) {
if(event.detail.status === 'FINISHED') {
let accountVariable = event.detail.outputVariables?.find(
outputVariable => outputVariable.name === 'NewAccount'
);
this.navigateToRecordPage(accountVariable.value.Id);
}
}
/**
*
* @param {string} recordId Id of the record
* @description This method is used to navigate the current user to the detail page of the record whose id is passed as parameter
*/
navigateToRecordPage(recordId) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: recordId,
actionName: 'view',
},
});
}
}</pre><div>Let's understand the above code line by line. First of all we imported <b>NavigationMixin</b> from <b>lightning/navigation</b> library. We're going to navigate to the record page of our newly created account record using this method. We also updated our FlowContainer class to extend <b>NavigationMixin(LightningElement)</b> for the same purpose.</div><div><br /></div><div>We introduced three variables in our js file:</div><div><b><br /></b></div><div>The first one is <b>showFlow</b>, which is a boolean variable and is <b>false</b> by default. This variable will be toggled to <b>true</b> when we want to display the flow in our LWC component. Remember the <b>lwc:if</b> condition in our HTML which is using this variable? If you check the HTML again you'll notice that by default our <b>lightning-button</b> will be visible through the <b>lwc:else</b> condition as <b>showFlow</b> is false.</div><div><br /></div><div>The second one is <b>contactIds</b>, which is a string array that consist of the ids of contact records. We're going to pass this array to our flow <b>ids</b> variable.</div><div><br /></div><div>The third one is <b>inputVariables</b> array, which is passed to our lightning-flow's <b>flow-input-variables</b> attribute. This is basically an array of objects, where each object is having a <b>name</b>, a <b>type</b> and a <b>value</b>. For each variable that we want to populate in our flow, through our LWC, we should have an entry for that in this array.</div><div><br /></div><div>In each object inside our inputVariables array:<br /></div><div><ul style="text-align: left;"><li>The <b>name</b> should be the name of the variable as defined in our flow. For our entry it's <b>ids</b> as we defined <b>ids</b> variable in the flow which will store the contact ids.<br /></li><li>The <b>type</b> is the data type of the variable. In our case it's <b>String</b> (Text in flow)<br /></li><li>The <b>value</b> should be the value of the variable defined in our flow. For our scenario, we want to pass the list of contact ids as value to our ids variable. Therefore, we've referred to our <b>contactIds</b> variable here which is defined above in the js and passed that as the value for our <b>ids</b> flow variable.</li></ul><div><b><br /></b></div><div><b>Note:</b> I'm re-iterating the same thing so that you don't miss this. If you notice, this is the point where we've defined that: in the <b>ids</b> variable of our screen flow, we want to pass the array of contact ids we've hardcoded in our js. This value can come from different sources depending upon your use case. For example: You can have a lightning-datatable where you can select some records and as the records are selected, you can populate the <b>contactIds</b> array. This array will automatically be passed to the <b>ids</b> variable in the flow as the flow is launched. </div></div><div><br /></div><div>After that we've defined 3 methods which are as follows:<br /><br /></div><div><ol style="text-align: left;"><li><b>launchFlow()</b>: This method will be called when we click <b>Duplicate Contacts</b> button in our HTML. It'll set <b>showFlow</b> attribute to true which will automatically hide the button and will show the screen flow in our LWC. In a way we can say, this method is used to launch our screen flow.<br /><br /></li><li><b>handleStatusChange(event)</b>: This method will be called whenever the flow status is updated. It's binded to <b>statuschange</b> event of our <b>lightning-flow</b> component. Inside this method, we need to check: If the flow is finished, we need to redirect the user to the detail page of newly created account record. We're receiving event inside this method as a parameter so, we're basically checking here if: <b>event.detail.status === FINISHED</b> i.e. if the flow is finished, we're referring to the <b>outputVariables</b> in our flow using <b>event.detail.outputVariables</b> to get the newly created account record. <br /><br />Remember that <b>Available for output</b> checkbox which we checked in our <b>NewAccount</b> variable inside the flow? That was marked so that it's value can be accessed outside the flow. Therefore, that variable will be received in the array of our <b>ouputVariables</b> accessed through event.detail.outputVariables.<br /><br />I'm sharing the NewAccount flow variable below again for your reference:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6zF1tiAE_Fsf1cTLrU5nyOa_XPxOJEVHgpsUiYyzlYRwoHCLharEcBMax8jO_8Ltasb2CTf82OA9xCI2ooKT1l4pJvWkh6fRI4GiMEbQ_KLweX9xOzbEsp74aqHmudX6635cfhr5N7cOHqlvmaI-OU64CC9_5ZkK6caOexgN7-gB4kzEiAfLrcBhEMQY/s2880/Screenshot%202024-01-15%20at%201.32.42%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1312" data-original-width="2880" height="292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6zF1tiAE_Fsf1cTLrU5nyOa_XPxOJEVHgpsUiYyzlYRwoHCLharEcBMax8jO_8Ltasb2CTf82OA9xCI2ooKT1l4pJvWkh6fRI4GiMEbQ_KLweX9xOzbEsp74aqHmudX6635cfhr5N7cOHqlvmaI-OU64CC9_5ZkK6caOexgN7-gB4kzEiAfLrcBhEMQY/w640-h292/Screenshot%202024-01-15%20at%201.32.42%E2%80%AFPM.png" width="640" /></a></div>As you can notice, we marked this as <b>Available for output</b> so that we can receive the value of this variable outside the flow i.e. in our case - inside our<b> flowContainer</b> lwc component. If you log <b>event.detail.outputVariables</b>, you'll get an array as shown below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5EvHsmLAvPZq0ZQ4TFMiXfLg-HSkZZ9nF-3mPSvn0DI9SKvv2DNG44gS0sRd-N7Gi5byqlM6AAyatOaBfwpQMCvr5uDVyEXm3RfC2NRs0V20CxZe4nthNClrxk2QWGVqOP-lFjimkBHKRvCTfRDIA9mWFWnFknZUg_I9Nkt54v7PFVPWtNGcZVf9nyWU/s934/Screenshot%202024-01-15%20at%204.06.14%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="864" data-original-width="934" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5EvHsmLAvPZq0ZQ4TFMiXfLg-HSkZZ9nF-3mPSvn0DI9SKvv2DNG44gS0sRd-N7Gi5byqlM6AAyatOaBfwpQMCvr5uDVyEXm3RfC2NRs0V20CxZe4nthNClrxk2QWGVqOP-lFjimkBHKRvCTfRDIA9mWFWnFknZUg_I9Nkt54v7PFVPWtNGcZVf9nyWU/w400-h370/Screenshot%202024-01-15%20at%204.06.14%E2%80%AFPM.png" width="400" /></a></div>As you can see above, there is only a single entry in this array and that is for our <b>NewAccount</b> variable. It's <b>name</b> property is having a value as <b>NewAccount</b>, it's <b>objectType</b> is <b>Account</b>. This variable is not a collection so <b>isCollection</b> is <b>false</b>. It's coming from flow <b>DuplicateContacts</b> so the flow name is the same and it's <b>dataType</b> is <b>SOBJECT</b> as it stores the value of an sObject record. Moving onto the <b>value</b> property, as we only populated the name of the account while creating the record from flow, it's value is an object having only two properties: <b>Id</b> and <b>Name</b>.<br /><br />Moving back to our code, we are using <b>find()</b> method to find the outputVariable from our <b>outputVariables</b> array where <b>name</b> = <b>NewAccount</b>. This will return us the first entry of our array. We're storing this in a js variable named <b>accountVariable</b>. Finally, we're accessing this account record's id using <b>accountVariable.value.Id</b> and passing it to our <b>navigateToRecordPage()</b> method which will navigate the current user to the new account's record page.<br /><br /><b>Note:</b> This is the point where we're receiving data from flow in our LWC component. The variables present in our flow which are marked as available for output, will be received in our LWC component inside <b>outputVariables</b> array under our event.<br /><br /></li><li><b>navigateToRecordPage(recordId)</b>: This method is used to navigate the current user to the detail page of the record whose record id is passed as a parameter. This method is called from <b>handleStatusChange()</b> method. We're using <b>NavigationMixin.Navigate</b> to navigate to the standard record page and we're passing recordId as the value to our <b>recordId</b> attribute.</li></ol><div><b><br /></b></div><div><b>That's all for our js part as well!</b> Let's embed our component inside the homepage and see how it works. Take a look at the demo below:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvJvtwS356PJJ3eRYwXAMqHFAlOM6BIBHm3ptqDA7VFudtAUrvdS1877sZX1ScDN74hANSdcvzsXPukDqxvxbWDqkKiCyeEk0SU-Knh3vuRrykmI2bznhhsn_1gfDOGLDHAzqwW8rP0pSJTyGR0Z15ZXoatkDFZOlGZOlVLiBomR3G3sBYyA8M_u_-M_Y/s1432/CPT2401151632-1432x618.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="618" data-original-width="1432" height="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvJvtwS356PJJ3eRYwXAMqHFAlOM6BIBHm3ptqDA7VFudtAUrvdS1877sZX1ScDN74hANSdcvzsXPukDqxvxbWDqkKiCyeEk0SU-Knh3vuRrykmI2bznhhsn_1gfDOGLDHAzqwW8rP0pSJTyGR0Z15ZXoatkDFZOlGZOlVLiBomR3G3sBYyA8M_u_-M_Y/w640-h276/CPT2401151632-1432x618.gif" width="640" /></a></div><div>As you can see above, as we clicked on <b>Duplicate Contacts</b> button in our LWC, the screen flow launched. We entered the account name in the screen flow which created a new account named <b>My Account</b> and all the 3 contacts (whose ids we hardcoded in our js) are duplicated and attached to this new account record. We finally received this new account record in our js as the flow finished and navigated to the account detail page using our LWC.</div><div><br /></div><div>If you see below, we have all the 3 newly created contacts linked to the same <b>My Account</b> record, however the old contacts are present as is and linked to their own account records.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYYCGieSkKm6uKh-01_h4RgvE_EYUomfeW1W3hwSiuWNh1QwXH99T15HKBAwyY2ikjkllOtLZVg4_baB2Cl6Tj_snhFL2jZ_oPg5OBrrZJG7DiLDJXo5afy-hqKmWgK_cQ37oEIRb4yv02SN7bS1NeYTbtqjqMK3fkLwr8QWNZfpvRmpj5kQTylWrsxXY/s2880/Screenshot%202024-01-15%20at%205.07.26%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="986" data-original-width="2880" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYYCGieSkKm6uKh-01_h4RgvE_EYUomfeW1W3hwSiuWNh1QwXH99T15HKBAwyY2ikjkllOtLZVg4_baB2Cl6Tj_snhFL2jZ_oPg5OBrrZJG7DiLDJXo5afy-hqKmWgK_cQ37oEIRb4yv02SN7bS1NeYTbtqjqMK3fkLwr8QWNZfpvRmpj5kQTylWrsxXY/w640-h220/Screenshot%202024-01-15%20at%205.07.26%E2%80%AFPM.png" width="640" /></a></div><div><br /></div>That's all for this tutorial, I hope you liked it. Let me know your feedback in the comments down below.<br /><div><br /></div><div><b>Happy Trailblazing!!</b></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-69745250142390328462024-01-07T20:30:00.000-08:002024-01-07T20:30:00.142-08:00Log LWC Event Messages using Lightning Logger : Event Monitoring in SalesforceHello Trailblazers,<div><br /></div><div>In this post we're going to talk about <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-logger" target="_blank"><span style="color: #2b00fe;">lightning/logger</span></a> module. This module can be used to log messages to salesforce event monitoring from your lightning web components. You can log the error messages as well as any kind of interactions that a user is having with your LWC component. <b>Let's see how!</b></div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg152y4DxFaBnVf3YYtSHjAG3lIz29Ls8LVNQzqeGjAAKfN0BlxDHL1UpPMnfE7hwGT3TFbYJSxP5PWyaaLiX3fiZHwKy5-ChVbVWHa2xyFC4LiMyY34Ppsmkg6uoaNnJYRXiLySFQzdMnolOSCnRqLAw5ovXSggzrezEUhTihapFBfzy5ATQHCYupUhhc/s1936/Screenshot%202023-12-30%20at%2010.36.26%E2%80%AFAM.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="650" data-original-width="1936" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg152y4DxFaBnVf3YYtSHjAG3lIz29Ls8LVNQzqeGjAAKfN0BlxDHL1UpPMnfE7hwGT3TFbYJSxP5PWyaaLiX3fiZHwKy5-ChVbVWHa2xyFC4LiMyY34Ppsmkg6uoaNnJYRXiLySFQzdMnolOSCnRqLAw5ovXSggzrezEUhTihapFBfzy5ATQHCYupUhhc/w640-h214/Screenshot%202023-12-30%20at%2010.36.26%E2%80%AFAM.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">A simple LWC component logging a message using Salesforce Event Monitoring on button click</td></tr></tbody></table><div><br /></div><div>Before jumping onto the code, I just want to share a one liner about Salesforce Event Monitoring. Event Monitoring is basically a tool (EventLogFile object) in salesforce that you can use to monitor events in your org and keep your data secure. It can track different types of events like: login, logout, web clicks, apex executions, report exports etc. You can also enable event monitoring for your custom LWC components and can use the library to log events in this EventLogFile object. You can learn more about event monitoring in this <a href="https://trailhead.salesforce.com/content/learn/modules/event_monitoring" target="_blank"><span style="color: #2b00fe;">trailhead module</span></a>.</div><h3 style="text-align: left;">Turn on Event Monitoring for LWC</h3><div>In order to turn on event monitoring for lightning web components, we can go to <b>Setup->Event Monitoring Settings</b> and turn on the switch for <b>Enable Lightning Logger Events</b> as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIJ8qguS1-hQ7oQzDH6c-WZN3nOx4luxxoKuGKHeK4KtpR6mrQIRbDoT6YN-06lu07WJxcRpzGN6vOO9wb_TDrdOwUccjT7lOH1PuHCKQKoepuuw2WmNvdv6spGm9ufgSwKW1DWOCwn2r_9NCG2r-ZHoD9Lc2aNL6wpsQqfJSl09Gx5RHb9-tueSrqpnM/s2880/Screenshot%202023-12-30%20at%2011.49.47%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1534" data-original-width="2880" height="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIJ8qguS1-hQ7oQzDH6c-WZN3nOx4luxxoKuGKHeK4KtpR6mrQIRbDoT6YN-06lu07WJxcRpzGN6vOO9wb_TDrdOwUccjT7lOH1PuHCKQKoepuuw2WmNvdv6spGm9ufgSwKW1DWOCwn2r_9NCG2r-ZHoD9Lc2aNL6wpsQqfJSl09Gx5RHb9-tueSrqpnM/w640-h340/Screenshot%202023-12-30%20at%2011.49.47%E2%80%AFAM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">Make sure <b>Generate event log files</b> switch is turned on as well.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Note: </b>As per <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_lc_component_instrumentation.htm&release=248&type=5" target="_blank"><span style="color: #2b00fe;">salesforce documentation</span></a>, <b>This change is available to customers who purchased Salesforce Shield or Salesforce Event Monitoring add-on subscriptions.</b></div><div class="separator" style="clear: both; text-align: left;"><b><br /></b></div><div class="separator" style="clear: both; text-align: left;">Let's begin by creating our LWC component:</div><h3 style="text-align: left;">eventLogDemo.html</h3><div>I created a new LWC component named <b>eventLogDemo</b>. The HTML code for the same is provided below:</div><pre><template>
<lightning-button label="Click Me!" onclick={createEventLog}></lightning-button>
</template></pre><div>As you can see above, I defined a simple button with label <b>Click Me!</b> and on clicking of this button, I'm calling my <b>createEventLog()</b> method which we're going to define in our js</div><h3 style="text-align: left;">eventLogDemo.js</h3><pre>import { LightningElement } from 'lwc';
import { log } from 'lightning/logger';
export default class EventLogDemo extends LightningElement {
createEventLog() {
log('Click Me button clicked!');
console.log('Event Log created!');
}
}</pre><div>For the js part, first of all, I imported <b>log</b> method from <b>lightning/logger</b> library. Inside our EventLogDemo class, I defined <b>createEventLog()</b> method which we're calling on clicking the button. This method is calling the log method we imported from the module and is passing a string message in the parameter i.e. <b>Click Me button clicked!</b>. After that, we're having a console.log statement as <b>Event Log created!</b> which is the message printed on the console.</div><h3 style="text-align: left;">eventLogDemo.js-meta.xml</h3><div><p>We also did common changes in our meta.xml file to embed this component in our homepage as shown below:</p><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Event Log Demo</masterLabel>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre><p>Now, it's time to do see this in action. Let's begin!</p></div><h3 style="text-align: left;">Demo</h3><div>I enabled <b>Debug Mode</b> for my user, so that I can see additional console.logs as well along with the one I have in my component. You can do the same by navigating to <b>Setup -> Debug Mode</b> and enabling it for your user as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdDNWZOTR8t-LJbL9iQ-8UUsHPjhJBT5KyimFx2Mb8YkkWUCIfPj4KkxVnliZtdaZJiZ72IeBV564r4Iwvrn5nqWgW2HNst0UkNWdnVwIa3OrR4IjTtDXvPKSXbWRThrx4iZKV8xZNxY7XEMr_KsRadNsftVgXnlsgvlv8yThICyBayQHTO24DQ-mCsb8/s1204/Screenshot%202023-12-30%20at%2010.33.52%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1174" data-original-width="1204" height="624" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdDNWZOTR8t-LJbL9iQ-8UUsHPjhJBT5KyimFx2Mb8YkkWUCIfPj4KkxVnliZtdaZJiZ72IeBV564r4Iwvrn5nqWgW2HNst0UkNWdnVwIa3OrR4IjTtDXvPKSXbWRThrx4iZKV8xZNxY7XEMr_KsRadNsftVgXnlsgvlv8yThICyBayQHTO24DQ-mCsb8/w640-h624/Screenshot%202023-12-30%20at%2010.33.52%E2%80%AFAM.png" width="640" /></a></div>I embedded my component on the homepage and as I clicked on the <b>Click Me!</b> button, I received an output as shown below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYVc-PudxbVcEwDCO73AbP3cP2zxj2X2IJB2DqD4aNKnvvZ664XLOd5M2nQDbYIZwyxaB3Sh35RtXEBDkOpHTAPKK6EtFxIZCsty6vew4NR-s8dl92K4C86aP47rT1jvud0zIPhTYhTus-rbfghqb4ZBwge6Vcq0X67QlIJNvVlxuYQsEkUMkgGOodRQU/s1936/Screenshot%202023-12-30%20at%2010.36.26%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="650" data-original-width="1936" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYVc-PudxbVcEwDCO73AbP3cP2zxj2X2IJB2DqD4aNKnvvZ664XLOd5M2nQDbYIZwyxaB3Sh35RtXEBDkOpHTAPKK6EtFxIZCsty6vew4NR-s8dl92K4C86aP47rT1jvud0zIPhTYhTus-rbfghqb4ZBwge6Vcq0X67QlIJNvVlxuYQsEkUMkgGOodRQU/w640-h214/Screenshot%202023-12-30%20at%2010.36.26%E2%80%AFAM.png" width="640" /></a></div>If you notice above, as I called <b>log()</b> method from the logger module, a message from client.js is printed on the console (because I have lightning components debug mode enabled). This object has the <b>message</b> as <b>Click Me button clicked!</b> which is the same that I passed in log() method. After this, we have our console.log() message printed as well i.e. <b>Event Log created!</b>.<div><br /></div><div>Now, In order to view our event logs, we can query them using the below query:</div><pre style="overflow-wrap: break-word; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;">SELECT Id, EventType, CreatedDate, LogFileLength, LogDate, ApiVersion, LogFileContentType, Sequence, Interval, LogFile FROM EventLogFile WHERE Interval = 'Hourly' AND EventType = 'LightningLogger'</pre><div><br /></div><div>I've mentioned <b>Interval = 'Hourly' AND EventType = 'LightningLogger'</b> to get only those event logs which are created using our LWC component. The result is provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHHhYWOt3pMFLrylduc3UCGnNdwKAqYwR_X0o3eAqN4Pxhb-j_jiOJy3bdJ7dGV_R3jKkjSsUu-S18wpjZE__ppu_7njv3q-E7pEHU0p-H4mkspKnHWttGg5NbkeQxUJyrUOCcJnTbBMXWvOrWGJAif7O_loUOUllnv6OVzCOScFq4nFwECk3sAEKlwP8/s2880/Screenshot%202023-12-30%20at%2011.01.00%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="352" data-original-width="2880" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHHhYWOt3pMFLrylduc3UCGnNdwKAqYwR_X0o3eAqN4Pxhb-j_jiOJy3bdJ7dGV_R3jKkjSsUu-S18wpjZE__ppu_7njv3q-E7pEHU0p-H4mkspKnHWttGg5NbkeQxUJyrUOCcJnTbBMXWvOrWGJAif7O_loUOUllnv6OVzCOScFq4nFwECk3sAEKlwP8/w640-h78/Screenshot%202023-12-30%20at%2011.01.00%E2%80%AFAM.png" width="640" /></a></div><div><b><br /></b></div><div><b>Note:</b> <b>I'm using a scratch org and it took somewhere about 2 hours for logs to start appearing after I generated them by clicking the button (the interval is Hourly for these). It might not be the same case for a production org</b> (maybe you can check this and let me know in the comments down below). However, in the <a href="https://trailhead.salesforce.com/content/learn/modules/event_monitoring/event_monitoring_intro#:~:text=An%20event%20log%20file%20is%20generated" target="_blank"><span style="color: #2b00fe;">Event Monitoring Trailhead</span></a>, it's written that <b>An event log file is generated when an event occurs in your organization and is available to view and download after 24 hours.</b> So you might have to wait more before you can access the event log files. It's also written that - all log files have 1 day data retention, you can increase it to 30 days for enterprise, unlimited and performance edition at an extra cost.</div><div><br /></div>If you want to download this log file, you can do that by calling the API as shown in the <b>LogFile</b> column as: /services/data/v59.0/sobjects/EventLogFile/0AT1y0000053cjXGAQ/LogFile. You can also download the same using <b>Salesforce Event Log File Browser</b> tool.<div><br /></div><div><b>Note</b>: Salesforce Event Log File Browser is not an official salesforce tool.</div><div><h3 style="text-align: left;">Salesforce Event Log File Browser</h3><div>To use this tool, go to: <a href="https://salesforce-elf.herokuapp.com/"><span style="color: #2b00fe;">https://salesforce-elf.herokuapp.com</span></a><div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYonQZYz9M75n5uHq_pSECGp4WmQz7YCzdtNVfLrIgAOmtS-05rlcRIjqpKHGjmjEcBG18Qv4n6Y3HhQv29we3T3RO-Cl-gexdV0ilj4f9kd3wZoUZeTqrXoFtKut5FNkJWhM6Xllkmdapo3d-C1n_-UPMIg4y-3IWnZsven7NYsxZwJ70ylTYI7Of8xM/s2880/Screenshot%202023-12-30%20at%2011.07.16%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="902" data-original-width="2880" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYonQZYz9M75n5uHq_pSECGp4WmQz7YCzdtNVfLrIgAOmtS-05rlcRIjqpKHGjmjEcBG18Qv4n6Y3HhQv29we3T3RO-Cl-gexdV0ilj4f9kd3wZoUZeTqrXoFtKut5FNkJWhM6Xllkmdapo3d-C1n_-UPMIg4y-3IWnZsven7NYsxZwJ70ylTYI7Of8xM/w640-h200/Screenshot%202023-12-30%20at%2011.07.16%E2%80%AFAM.png" width="640" /></a></div>You can click on <b>Production Login</b> if you're using a developer/production org or <b>Sandbox Login</b> if you're using a sandbox/scratch org. You'll get the OAuth screen as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9nnXJP2iZzWL5SnRnLZ7X-GotyHvsnxiooerqlNRJ33mr8YWxXcDZWNTPQY4P1zBACNf0V8UgS6gBgquQJGDllmx2bA0bTpRDq0rsu-UYTqxX5Jp-3Z1fyy6V4AmBJq6ON6MB0wLoYlHxAy7xqJY5MgRML6vaLYwzjQopXTHxk-EloEDBwI8vptXfv0o/s1212/Screenshot%202023-12-30%20at%2011.14.33%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1212" data-original-width="1056" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9nnXJP2iZzWL5SnRnLZ7X-GotyHvsnxiooerqlNRJ33mr8YWxXcDZWNTPQY4P1zBACNf0V8UgS6gBgquQJGDllmx2bA0bTpRDq0rsu-UYTqxX5Jp-3Z1fyy6V4AmBJq6ON6MB0wLoYlHxAy7xqJY5MgRML6vaLYwzjQopXTHxk-EloEDBwI8vptXfv0o/w558-h640/Screenshot%202023-12-30%20at%2011.14.33%E2%80%AFAM.png" width="558" /></a></div>Click on <b>Allow</b>. <br /><div><br /></div><div>You'll land to the below page where you can see all the event logs:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinLWoPCzTZs3H_AWAwyx65jfRxtdDK4cbaWb715Snd91htzvwUFIKug1_KUDkkPf5c5Ke_YWxR5XwdUoHcWkQMTVYD8TszUGTJOy_i14GCeEQDcX_2LFlAWsZV2gCukGr7GfT41801fekyW-SytX9Vq6gu3ZauwNB3es2K1n3lZkmcziPdPftgbvcHAMc/s2880/Screenshot%202023-12-30%20at%2011.40.51%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1626" data-original-width="2880" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinLWoPCzTZs3H_AWAwyx65jfRxtdDK4cbaWb715Snd91htzvwUFIKug1_KUDkkPf5c5Ke_YWxR5XwdUoHcWkQMTVYD8TszUGTJOy_i14GCeEQDcX_2LFlAWsZV2gCukGr7GfT41801fekyW-SytX9Vq6gu3ZauwNB3es2K1n3lZkmcziPdPftgbvcHAMc/w640-h362/Screenshot%202023-12-30%20at%2011.40.51%E2%80%AFAM.png" width="640" /></a></div>You can filter the results using the dropdowns/picklists present above. As you can see below, I selected the event type as <b>LightningLogger</b> and got the below output:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnCeZRYxP6efdXflTpb5yCBLnfo2quQFIDBctUnhicH4lOizmxFVeNxXjoNEocjtpu8eezZIjfIjlqgtACQY_cacldQd6v55L2DkjGIufwD5JbckRkFeFk-EjIuvdOFrGrr0TkR9jg6VgE8y3ObiUfmpqF-Y6FMhzNTKOmOtax8VXveAKIfBnZv-Hd3Ac/s2364/Screenshot%202023-12-30%20at%2011.43.43%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="708" data-original-width="2364" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnCeZRYxP6efdXflTpb5yCBLnfo2quQFIDBctUnhicH4lOizmxFVeNxXjoNEocjtpu8eezZIjfIjlqgtACQY_cacldQd6v55L2DkjGIufwD5JbckRkFeFk-EjIuvdOFrGrr0TkR9jg6VgE8y3ObiUfmpqF-Y6FMhzNTKOmOtax8VXveAKIfBnZv-Hd3Ac/w640-h192/Screenshot%202023-12-30%20at%2011.43.43%E2%80%AFAM.png" width="640" /></a></div>In the <b>Action</b> column, I have two buttons. I can download the CSV log file or a shell script - which will download the log file to my system. I downloaded the CSV log file and the output for the same is shown below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbsj7v2TVi9Uh_dNVMjCvbBm1G0nf7j_m6d_jO1vMZvhoU7G4E-v9q-DuZ64yvdBpeIT5VX3Pg4xlnMn4-SsjRPYzMDljWMFhBy_owJxHqAzqzIJ3jJ8-qEdKkZzgRT1feQG6WITiLFhhBDWzF_X9RSJXb0qsX1T8FaI-t6SjiJIZ37AVwmmDRgDCTheg/s2880/Screenshot%202023-12-30%20at%2012.04.54%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="428" data-original-width="2880" height="96" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbsj7v2TVi9Uh_dNVMjCvbBm1G0nf7j_m6d_jO1vMZvhoU7G4E-v9q-DuZ64yvdBpeIT5VX3Pg4xlnMn4-SsjRPYzMDljWMFhBy_owJxHqAzqzIJ3jJ8-qEdKkZzgRT1feQG6WITiLFhhBDWzF_X9RSJXb0qsX1T8FaI-t6SjiJIZ37AVwmmDRgDCTheg/w640-h96/Screenshot%202023-12-30%20at%2012.04.54%E2%80%AFPM.png" width="640" /></a></div>If you notice above, the <b>PAGE_URL</b> is <b>/lightning/page/home</b> which specifies that this log is triggered from our homepage as the lwc component is embedded in the homepage and the <b>MESSAGE</b> is: <b>Click Me button clicked!</b> which is the same as we passed to the <b>log()</b> method. The 3 entries here means that I clicked this button (or fired this event) 3 times during the hour for which this log file is generated. We can also pass a js object to our log method which is automatically stringified. The maximum string length is 4096 characters. I modified our js code a little bit to pass an object as shown below:</div><pre>import { LightningElement } from 'lwc';
import { log } from 'lightning/logger';
export default class EventLogDemo extends LightningElement {
msg = {
type: "click",
action: "Click Me button clicked"
};
createEventLog() {
log(this.msg);
log('Click Me button clicked!');
console.log('Event Log created!');
}
}</pre><div>As you can see above, I'm passing the <b>msg</b> object to the log() method which consist of two properties: <b>type</b> and <b>action</b>. The <b>type</b> is <b>click</b> and <b>action</b> is <b>Click Me button clicked</b>. As I click the button now, I get two messages from client.js along with my console.log() message as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfYjiHHoiGqVbNGIrF49jaoasTvsJXoHOndmatKzDYO2rNdc-UD_welDqrPAMGMgXGnIrW92cRkSjJIhxHkKTgr1n61T8ChIsNFFtVo4oCGm8WIRaBRF-DIQNOfmwmdNgh4FUf46EbzxALF3H9s0tgp4FQrrIPo9DoumD7jSSQ3yLuhlOb-lt88WoSpB4/s2544/Screenshot%202023-12-30%20at%2012.13.18%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="886" data-original-width="2544" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfYjiHHoiGqVbNGIrF49jaoasTvsJXoHOndmatKzDYO2rNdc-UD_welDqrPAMGMgXGnIrW92cRkSjJIhxHkKTgr1n61T8ChIsNFFtVo4oCGm8WIRaBRF-DIQNOfmwmdNgh4FUf46EbzxALF3H9s0tgp4FQrrIPo9DoumD7jSSQ3yLuhlOb-lt88WoSpB4/w640-h222/Screenshot%202023-12-30%20at%2012.13.18%E2%80%AFPM.png" width="640" /></a></div>This time we have two event logs generated. One is having the message as: <b>"{"type":"click","action":"Click Me button clicked"}"</b> and another one is having the message as <b>"Click Me button clicked!"</b>. The log file generated for the same is provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbBMItERjN-5vEnuIFV2x_Sjzh7_5WMZk9tzs3EZF3zq8uhhel6C6ndUHMFTf5myv56qvKUvFekCug5pxezOXf1jQfe1wve4eK5_MsN5LfjDRfZ4EHzXQK4eFJOR1FDRSisCUmcyqHsMoolNrpBMBmPcT_7SToH3nnAUiZ9u2mmKEqdbZAgLu7Y3yyQ_k/s2880/Screenshot%202023-12-30%20at%201.15.38%E2%80%AFPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="350" data-original-width="2880" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbBMItERjN-5vEnuIFV2x_Sjzh7_5WMZk9tzs3EZF3zq8uhhel6C6ndUHMFTf5myv56qvKUvFekCug5pxezOXf1jQfe1wve4eK5_MsN5LfjDRfZ4EHzXQK4eFJOR1FDRSisCUmcyqHsMoolNrpBMBmPcT_7SToH3nnAUiZ9u2mmKEqdbZAgLu7Y3yyQ_k/w640-h78/Screenshot%202023-12-30%20at%201.15.38%E2%80%AFPM.png" width="640" /></a></div><div>As you can see, this time our whole object is also coming as message in the logs.</div><div><br /></div><div>This is how, you can log your custom LWC event messages and view them using salesforce event monitoring tool. That's all for this tutorial, I hope you liked it. Let me know your feedback in the comments down below.<div><br /></div><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com1tag:blogger.com,1999:blog-8643239839712475364.post-25736495078421902662023-12-31T20:30:00.000-08:002023-12-31T20:30:00.143-08:00LWC Lookup Component by Salesforce: Lightning Record Picker<p>Hello Trailblazers,</p><p><br /></p><p>In this post, we're going to learn about <b>lightning-record-picker</b> which is basically an input field using which you can search for salesforce records. The basic code to implement the same is provided below:</p><pre><template>
<lightning-card hide-header label="Account Record Picker Card">
<p class="slds-var-p-horizontal_small">
<lightning-record-picker
label="Select Account"
placeholder="Type Something..."
object-api-name="Account"
></lightning-record-picker>
</p>
</lightning-card>
</template></pre><p><br /></p><p>As you can see above, I used the <b>lightning-record-picker</b> tag where I've setup the <b>label</b> as <b>Select Account</b>, the <b>placeholder</b> is <b>Type Something...</b> and the <b>object-api-name</b> is <b>Account</b>. You can ignore the lightning-card. I've added that only for a good white background as we're going to embed this component in our homepage. The result is shown below:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFxZTVFhBX6HXvU5mA0iT-UVYlCgsb5RHIN-ESNYSH8j9_agYSYwgCgUhEmL2FW7uoA7Y7HBN_SV3X9GEnX3zIscVy47oZuF6M5TV1vX0LbZ-6qICXX-0c4tgdXKm4aYzs3FxSMp55C55MKw2ZxrJkl5K4zgPk-0sDdw4BHixx59O2oZObDbD_Ee1mWkU/s475/lightning-record-picker.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="390" data-original-width="475" height="329" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFxZTVFhBX6HXvU5mA0iT-UVYlCgsb5RHIN-ESNYSH8j9_agYSYwgCgUhEmL2FW7uoA7Y7HBN_SV3X9GEnX3zIscVy47oZuF6M5TV1vX0LbZ-6qICXX-0c4tgdXKm4aYzs3FxSMp55C55MKw2ZxrJkl5K4zgPk-0sDdw4BHixx59O2oZObDbD_Ee1mWkU/w400-h329/lightning-record-picker.gif" width="400" /></a></div><br /><p>We also did common changes in our meta.xml file to embed this component in our homepage as shown below:</p><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Record Picker Demo</masterLabel>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre><p>Now, it's time to do some more changes. Let's begin!</p><h3 style="text-align: left;">Add filter to our lookup component</h3><p>Let's add a default filter to our record picker component such that it'll search only those accounts whose <b>Rating</b> is <b>equal</b> to <b>Warm</b>. It's time to update our js file now:</p><pre>import { LightningElement } from 'lwc';
export default class RecordPickerDemo extends LightningElement {
filter = {
criteria: [
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Warm'
}
]
};
}</pre><p>As you can see above, we defined a <b>filter</b> object that consist of a single property named <b>criteria</b>. This property is an array which can have multiple objects, each having 3 properties:</p><p><br /></p><p>1. <b>fieldPath</b>: API name of the field for the current object on which we've our record picker. You can also mention relationships upto one level, for example: Parent.Rating (considering the account object)</p><p><br /></p><p>2. <b>operator</b>: It can have different values depending upon the comparison we want to perform. The possible values are given below:</p><p><br /></p><p><b>eq</b> = Equal</p><p><b>ne</b> = Not Equal</p><p><b>lt</b> = Less Than</p><p><b>gt</b> = Greater Than</p><p><b>lte</b> = Less than or equal</p><p><b>gte</b> = Greater than or equal</p><p><b>in</b> = Similar to IN operator of SOQL</p><p><b>nin</b> = Similar to NOT IN operator of SOQL</p><p><b>like</b> = Similar to LIKE operator of SOQL</p><p><b>includes</b> = Check the result should include provided values</p><p><b>excludes</b> = Check the result should exclude provided values</p><p><br /></p><p>Different keywords provided above are applicable for fields of different data types. The fields and the operator values they support are provided in the salesforce documentation <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-record-picker/documentation#:~:text=Supported%20Operator%20Values" target="_blank"><span style="color: #2b00fe;">here</span></a>. If you want to learn more about the keywords, you can check them in the GraphQL documentation <a href="https://developer.salesforce.com/docs/platform/graphql/guide/filter-fields.html#field-functions" target="_blank"><span style="color: #2b00fe;">here</span></a></p><p><br /></p><p>3. <b>value</b>: Value for the applied filter</p><p><br /></p><p>The updated html code to apply the filter we defined in js is provided below:</p><pre><template>
<lightning-card hide-header label="Account Record Picker Card">
<p class="slds-var-p-horizontal_small">
<lightning-record-picker
label="Select Account"
placeholder="Type Something..."
object-api-name="Account"
filter={filter}
></lightning-record-picker>
</p>
</lightning-card>
</template></pre><p>Notice that we've populated <b>filter</b> property of our <b>lightning-record-picker</b> with the <b>filter</b> variable that is defined in our js.</p><p>With this Rating filter applied by default, we can only see a subset of records. As you can see below, only 4 account records are present in my org with Rating as Warm</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilrNGrW2J_8jI3duwE6tOxPOOCOLdFTuKABvd3bTlqfCzO1TCO6fL0Twy3kcHf0u11fYUadYH8_NCaxuqrGzWiV9wyL8ifLIw996QmIwj1GFRbTSIazQl6SD4CGzgyPrTb65HWwVfxss7lfHPOumPQTSWaV-VpO6TbQ80AreEMVC5988INt5RQMSWsrb0/s2880/Screenshot%202023-12-28%20at%204.44.33%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1104" data-original-width="2880" height="246" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilrNGrW2J_8jI3duwE6tOxPOOCOLdFTuKABvd3bTlqfCzO1TCO6fL0Twy3kcHf0u11fYUadYH8_NCaxuqrGzWiV9wyL8ifLIw996QmIwj1GFRbTSIazQl6SD4CGzgyPrTb65HWwVfxss7lfHPOumPQTSWaV-VpO6TbQ80AreEMVC5988INt5RQMSWsrb0/w640-h246/Screenshot%202023-12-28%20at%204.44.33%E2%80%AFPM.png" width="640" /></a></div><p>If I search in my lookup now, the search is performed with this predefined Rating filter already applied to my record picker:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNKDQ0PQC72dibjRQHCNgYskHb6sRXthcmCp7KGrLLxHCPtFOC5WPL1fwzoFtBji4KfcIAggsUpfHUhLoRrz9IgBU69oIJtV-V8L4G6JA1iDFJC765RBrhgwV26M48xN5gCXQgDm76T6XXb2TZcRRgJenyfBsUG472XZMHMwsM7AwcO-BMpHxkYU6XSSI/s962/Screenshot%202023-12-28%20at%205.37.39%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="570" data-original-width="962" height="380" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNKDQ0PQC72dibjRQHCNgYskHb6sRXthcmCp7KGrLLxHCPtFOC5WPL1fwzoFtBji4KfcIAggsUpfHUhLoRrz9IgBU69oIJtV-V8L4G6JA1iDFJC765RBrhgwV26M48xN5gCXQgDm76T6XXb2TZcRRgJenyfBsUG472XZMHMwsM7AwcO-BMpHxkYU6XSSI/w640-h380/Screenshot%202023-12-28%20at%205.37.39%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">As you can see above, only these 4 records are visible as I search with keyword <b>o</b>. </div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">We can also specify a <b>filterLogic</b> property in our <b>filter</b> object. This property basically consist of logic to combine multiple filter criterias. For example: If in the current filter, we want to consider Cold rating as well, along with Warm rating, we can update our filter as shown below:</div><pre>import { LightningElement } from 'lwc';
export default class RecordPickerDemo extends LightningElement {
filter = {
criteria: [
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Warm'
},
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Cold'
}
],
filterLogic: '1 OR 2'
};
}</pre><div class="separator" style="clear: both; text-align: left;">As you can see above, I've added one more filter criteria that specify the Rating as Cold along with the existing filter for Rating as Warm. Also, I've specified the <b>filterLogic</b> as <b>1 OR 2</b>, where 1 corresponds to the first filter and 2 corresponds to the second filter, so basically here we're saying that the account rating should either be Warm or Cold. The updated results in our lookup are shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFlGNd4-9AGmcufyRN4q9_SmflztOjn3BEKmfiWaf5IAtMKeEj2oJstSvoJ304X5dIvRsVjdjWaW6Adbw1rTQWuPFxsJ8hwSmWaxVrfENuFGdA5xra0EpbDM9MDOKG0HZR9z_eMptPbYefnkPLY1jSDNjrF1XbXNmCTIO35OqisLcY9Sw54zbNDfPkLZ8/s950/Screenshot%202023-12-28%20at%206.23.12%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="594" data-original-width="950" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFlGNd4-9AGmcufyRN4q9_SmflztOjn3BEKmfiWaf5IAtMKeEj2oJstSvoJ304X5dIvRsVjdjWaW6Adbw1rTQWuPFxsJ8hwSmWaxVrfENuFGdA5xra0EpbDM9MDOKG0HZR9z_eMptPbYefnkPLY1jSDNjrF1XbXNmCTIO35OqisLcY9Sw54zbNDfPkLZ8/w640-h400/Screenshot%202023-12-28%20at%206.23.12%E2%80%AFPM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Note:</b> By default, if no filterLogic is defined, all filter criterias are applied using the keyword <b>AND</b>.</div><h3 style="text-align: left;">Display Additional Field in our Record Picker (Lookup) Search Results</h3><div>Now, it's time to display an additional field value in our record picker search result. We can display only one field from the same/related object as the additional field. For example: if we want to display the rating as well - in the search results, we can do that as follows:</div><pre>import { LightningElement } from 'lwc';
export default class RecordPickerDemo extends LightningElement {
filter = {
criteria: [
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Warm'
},
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Cold'
}
],
filterLogic: '1 OR 2'
};
displayInfo = {
additionalFields: ['Rating']
}
}</pre><div>If you notice above, we defined one more object named <b>displayInfo</b>. It has a property called <b>additionalFields</b> which is an array with single string value i.e. the API name of our additional field to query which is <b>Rating</b> in our case. I can use this displayInfo object and pass it to our record picker component in the html as shown below:</div><pre><template>
<lightning-card hide-header label="Account Record Picker Card">
<p class="slds-var-p-horizontal_small">
<lightning-record-picker
label="Select Account"
placeholder="Type Something..."
object-api-name="Account"
filter={filter}
display-info={displayInfo}
></lightning-record-picker>
</p>
</lightning-card>
</template></pre><div>If you noticed above, I added another property to our lightning-record-picker named <b>display-info</b> and it's referring to our <b>displayInfo</b> object which we created in our js. Now, the search results output is also displaying the rating as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicKjg79VNmgkfNKLzffBQ60pdidxqHj7CZ9KXugn4055FT5bhoa4UZBByi8wlILamlmIJURsWeqD7Pa4plNQC-iABTHkUSSi8D5ZafFedVLPpR4hqtBXQiqoOO5lcgY5R4m1jWe9VDO_foTHUZM1xoFFxNpocjMKbWEDIIbNbuM_ra0baDlDC_zz4WkAc/s962/Screenshot%202023-12-28%20at%206.40.33%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="742" data-original-width="962" height="494" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicKjg79VNmgkfNKLzffBQ60pdidxqHj7CZ9KXugn4055FT5bhoa4UZBByi8wlILamlmIJURsWeqD7Pa4plNQC-iABTHkUSSi8D5ZafFedVLPpR4hqtBXQiqoOO5lcgY5R4m1jWe9VDO_foTHUZM1xoFFxNpocjMKbWEDIIbNbuM_ra0baDlDC_zz4WkAc/w640-h494/Screenshot%202023-12-28%20at%206.40.33%E2%80%AFPM.png" width="640" /></a></div><div><br /></div><div>As you can see, we're only getting accounts with rating Warm or Cold.</div><h3 style="text-align: left;">Can we query using a different field?</h3><p>By default, records are queried using the name field. However, we can use a different primary field to query records as well. We can also specify an additional field to query records. Let's say we want to query records using Rating field. The updated code for the js file is provided below:</p><pre>import { LightningElement } from 'lwc';
export default class RecordPickerDemo extends LightningElement {
filter = {
criteria: [
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Warm'
},
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Cold'
}
],
filterLogic: '1 OR 2'
};
displayInfo = {
additionalFields: ['Rating']
}
matchingInfo = {
primaryField: { fieldPath: 'Rating' }
}
}</pre><p>If you notice above, I've defined a new object named <b>matchingInfo</b>. In this object, we can define primaryField as well as additionalFields that we want to use to query records. The <b>primaryField</b> is basically an object with single property named <b>fieldPath</b> which should have the API name of the field you want to use. For our example, the API name is <b>Rating</b> for the Rating field of account. Also, the updated html is shown below:</p><pre><template>
<lightning-card hide-header label="Account Record Picker Card">
<p class="slds-var-p-horizontal_small">
<lightning-record-picker
label="Select Account"
placeholder="Type Something..."
object-api-name="Account"
filter={filter}
display-info={displayInfo}
matching-info={matchingInfo}
></lightning-record-picker>
</p>
</lightning-card>
</template></pre><p>As you can see above, the matchingInfo property from our js is assigned to <b>matching-info</b> property of our lightning-record-picker.</p><p>Now, the records will be queried on the basis of <b>Rating</b> and not <b>Name</b> as shown below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJWNZvTMB4sJ-EGz7zHlTBgxI0n0TB3DPKPkUDGo73mKvXA8bwc2pPVWCiSPZWJuntXuMKNPop9ET18VWMNvJ8SDoCFw5dqh8eHwglH2PSWgK_jCwCbfwQciFK2UAcqG9pUDP1d0d7xQVz9_qnfv4jAa1jBgyvlzuZ2sPDqDfBkAdPNzauLNuLXlvuZoM/s964/Screenshot%202023-12-28%20at%207.07.58%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="576" data-original-width="964" height="382" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJWNZvTMB4sJ-EGz7zHlTBgxI0n0TB3DPKPkUDGo73mKvXA8bwc2pPVWCiSPZWJuntXuMKNPop9ET18VWMNvJ8SDoCFw5dqh8eHwglH2PSWgK_jCwCbfwQciFK2UAcqG9pUDP1d0d7xQVz9_qnfv4jAa1jBgyvlzuZ2sPDqDfBkAdPNzauLNuLXlvuZoM/w640-h382/Screenshot%202023-12-28%20at%207.07.58%E2%80%AFPM.png" width="640" /></a></div><div><br /></div>We can update our code to define an additional field to be used to query records as well. Let's see the updated code:<pre>import { LightningElement } from 'lwc';
export default class RecordPickerDemo extends LightningElement {
filter = {
criteria: [
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Warm'
},
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Cold'
}
],
filterLogic: '1 OR 2'
};
displayInfo = {
additionalFields: ['Rating']
}
matchingInfo = {
primaryField: { fieldPath: 'Rating' },
additionalFields: [ { fieldPath: 'Phone' } ]
}
}</pre><div>As you can see above, we defined another property named <b>additionalFields</b> which is an array where we can define additional fields. We can define only one additional field here. For our use case, I used the <b>Phone</b> field as an additional field. Now, I can also query records using Phone field as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9H7emgmTAxuWaNItXxcRw5XNFL9yipb4hfBJGzKABR609ImEULZGvCxyHTfWiFeatgf3_RRlwwmJFqpMecCnPUJrFUa4qPFNDih2dWpmtbsVmg4d-suOJVLInpud-6sBzAP3YsBXBhPTxpsUX_NsVLnJnlJLquhNqnrutDqhV__Z_sNZHbSEnyoBwk7s/s960/Screenshot%202023-12-28%20at%207.38.53%E2%80%AFPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="960" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9H7emgmTAxuWaNItXxcRw5XNFL9yipb4hfBJGzKABR609ImEULZGvCxyHTfWiFeatgf3_RRlwwmJFqpMecCnPUJrFUa4qPFNDih2dWpmtbsVmg4d-suOJVLInpud-6sBzAP3YsBXBhPTxpsUX_NsVLnJnlJLquhNqnrutDqhV__Z_sNZHbSEnyoBwk7s/w640-h212/Screenshot%202023-12-28%20at%207.38.53%E2%80%AFPM.png" width="640" /></a></div><div><h3 style="text-align: left;">Making the lightning-record-picker required</h3><div>We can make our lightning-record-picker required as well using the <b>required</b> attribute. The updated HTML code is provided below:</div><pre><template>
<lightning-card hide-header label="Account Record Picker Card">
<p class="slds-var-p-horizontal_small">
<lightning-record-picker
label="Select Account"
placeholder="Type Something..."
object-api-name="Account"
filter={filter}
display-info={displayInfo}
matching-info={matchingInfo}
required
></lightning-record-picker>
</p>
</lightning-card>
</template></pre><div>As you can see above, I've added the required attribute to my lightning-record-picker tag. This is a boolean attribute so we don't need to specify any value. Just adding required means, it's true. We don't need to add it if we don't want to make our field as required. Now, if we don't select a record, by clicking on the record picker, we'll get the error message as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaHMpcdHq3BK1pkMRO8Zk8hAX6Exzml_Tu95EgoS1aHNUzkjVjxVdIn9LWMl4iwCxOF7MjCAdfByjvlCd-GBskDgsl0NOCFyrO-a4Vw7nzFKf1Z17breAEgym0YXJUifVmI3RUMqZsxaZSIK6aINE1bh6HuL7-OSajXjosQWSCTDQKAyPwj46RswA6EBM/s962/Screenshot%202023-12-29%20at%2011.02.45%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="258" data-original-width="962" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaHMpcdHq3BK1pkMRO8Zk8hAX6Exzml_Tu95EgoS1aHNUzkjVjxVdIn9LWMl4iwCxOF7MjCAdfByjvlCd-GBskDgsl0NOCFyrO-a4Vw7nzFKf1Z17breAEgym0YXJUifVmI3RUMqZsxaZSIK6aINE1bh6HuL7-OSajXjosQWSCTDQKAyPwj46RswA6EBM/w640-h172/Screenshot%202023-12-29%20at%2011.02.45%E2%80%AFAM.png" width="640" /></a></div>As you can see, there is a red asterisk (<span style="color: red;">*</span>) with <b>Select Account</b> label as well which specifies that the field is required.<h3 style="text-align: left;">Setting a default record using record id as the component is loaded</h3><div>One common use case we can encounter is to setup a default record for our lightning-record-picker as it's loaded initially. We can do that using the <b>value</b> attribute. Let's do some changes in our js first:</div><pre>import { LightningElement } from 'lwc';
export default class RecordPickerDemo extends LightningElement {
filter = {
criteria: [
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Warm'
},
{
fieldPath: 'Rating',
operator: 'eq',
value: 'Cold'
}
],
filterLogic: '1 OR 2'
};
displayInfo = {
additionalFields: ['Rating']
}
matchingInfo = {
primaryField: { fieldPath: 'Rating' },
additionalFields: [ { fieldPath: 'Phone' } ]
}
recordId = '001H3000002jHRYIA2';
}</pre><div>As you can see above, I've defined a new property in my js class named <b>recordId</b>. I have hardcoded it's value to the id of an account from my salesforce org. Now, I can update my HTML as well so that this account record is pre-selected in the lookup:</div><pre><template>
<lightning-card hide-header label="Account Record Picker Card">
<p class="slds-var-p-horizontal_small">
<lightning-record-picker
label="Select Account"
placeholder="Type Something..."
object-api-name="Account"
filter={filter}
display-info={displayInfo}
matching-info={matchingInfo}
required
value={recordId}
></lightning-record-picker>
</p>
</lightning-card>
</template></pre><div>If you notice above, the <b>value</b> attribute of my record picker is assigned the <b>recordId</b> property which I defined in my js file. Now, as the component is loaded initially, the account record with this record id is pre-selected as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixRz4A64fuGZ2u8dep8Qm2H2EU9xQfBYWx3qb948tuTYxnrHy7cGTWfvB774mWSGXpsS5bFKILzQPvrypQ9j-OZOoA6Hh67Lai8AvUSv3qmkmqmKnFjKP0pu7773tZycEetrRvY6G_Es3k4qAowSrA0ZNNqznbb1p9azL-4ksQ7BKcj491L-Gh_Mm1QY4/s960/Screenshot%202023-12-29%20at%2011.13.54%E2%80%AFAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="234" data-original-width="960" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixRz4A64fuGZ2u8dep8Qm2H2EU9xQfBYWx3qb948tuTYxnrHy7cGTWfvB774mWSGXpsS5bFKILzQPvrypQ9j-OZOoA6Hh67Lai8AvUSv3qmkmqmKnFjKP0pu7773tZycEetrRvY6G_Es3k4qAowSrA0ZNNqznbb1p9azL-4ksQ7BKcj491L-Gh_Mm1QY4/w640-h156/Screenshot%202023-12-29%20at%2011.13.54%E2%80%AFAM.png" width="640" /></a></div><p>That's all for this tutorial, I hope you liked it. There are also some predefined events linked to lightning-record-picker which you can check in the official documentation <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-record-picker/documentation#:~:text=Custom%20Events" target="_blank"><span style="color: #2b00fe;">here</span></a>. Let me know if you need a tutorial for the same and I can create one. I will look forward to your feedback in the comments down below.</p><p><br /></p><p><b>Happy Trailblazing!!</b></p></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-82774481998948018282023-10-31T08:39:00.006-07:002023-11-05T11:23:53.450-08:00Introducing Mastering Lightning Datatable in Salesforce LWC Course on Udemy<p>Hello Trailblazers,</p><p><br /></p><p>I'm super excited to launch my first course on udemy titled <b>Mastering Lightning Datatable in Salesforce LWC</b>. In this course, you'll learn about almost every aspect of lightning datatable from scratch and you'll level up as a salesforce developer. I am giving away 100 free coupons to our amazing community. Fill in the below form and stand a chance to win the course for free!</p><iframe frameborder="0" height="1350" marginheight="0" marginwidth="0" src="https://docs.google.com/forms/d/e/1FAIpQLSd3s86YQ1DuqqyESCAB7twzuQhYkl2e0XceNM6lsWSzKRK7kA/viewform?embedded=true" width="640">Loading…</iframe><div>However, if you're interested to buy this course to get immediate access, you can buy it using this link: <a href="https://www.udemy.com/course/mastering-lightning-datatable-in-salesforce-lwc/?referralCode=0354EB92E7BB30C0B980"><span style="color: #2b00fe;">https://www.udemy.com/course/mastering-lightning-datatable-in-salesforce-lwc/?referralCode=0354EB92E7BB30C0B980</span></a></div><div><br /><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-11982519516997443292023-08-14T09:43:00.004-07:002023-08-14T09:43:49.563-07:00Lifecycle Hooks in LWC<p>Hello Trailblazers,</p><p><br /></p><p>In this post we'll understand different lifecycle hooks of lightning web components.</p><p><br /></p><h2 style="text-align: left;">What is a Lifecycle Hook?</h2><div>A lifecycle hook is a callback method which is called automatically at some point during a lightning web component's lifecycle. In simple words, I can say: From the moment a component is initialized, till the moment it's removed from the page, there are various important instances that can be used by developers to perform various actions. So, in order to allow the developers to perform those actions, some callback methods are automatically called during these instances.</div><div><br /></div><h2 style="text-align: left;">What are the different lifecycle hooks of lightning web components?</h2><div>There are mainly 5 lifecycle hooks in lightning web components. They are:</div><div><br /></div><div><ol style="text-align: left;"><li>constructor()<br /><br /></li><li>connectedCallback()<br /><br /></li><li>renderedCallback()<br /><br /></li><li>disconnectedCallback()<br /><br /></li><li>errorCallback()</li></ol><div><br /></div><div>There is a great diagram present in the <a href="https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.create_lifecycle_hooks" target="_blank"><span style="color: #2b00fe;">salesforce official documentation</span></a> showcasing the lifecycle flow or in other words: the order in which these lifecycle hooks are being called. Let's take a look at that below:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://resources.docs.salesforce.com/images/57e856aff4175f807fe0ccbf6ad98301.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="800" data-original-width="604" height="640" src="https://resources.docs.salesforce.com/images/57e856aff4175f807fe0ccbf6ad98301.png" width="483" /></a></div><br /><div>This basically means that whenever a parent and child component is rendered on the page, the order of lifecycle hooks will be as follows:</div><div><br /></div><div><ol style="text-align: left;"><li><b>constructor()</b> called on <b>parent</b></li><li><b>connectedCallback()</b> called on <b>parent</b></li><li><b>constructor()</b> called on <b>child</b></li><li><b>connectedCallback()</b> called on <b>child</b></li><li><b>renderedCallback()</b> called on <b>child</b></li><li><b>renderedCallback()</b> called on <b>parent</b></li></ol><div><br /></div><div>In case of an error in child component, the <b>errorCallback()</b> of parent component will be called after the first 4 callbacks mentioned above i.e. before calling the <b>renderedCallback()</b> for child + parent component. The updated order in case of an error in child will be:<br /><br /></div></div><div><ol><li><b>constructor()</b> called on <b>parent</b></li><li><b>connectedCallback()</b> called on <b>parent</b></li><li><b>constructor()</b> called on <b>child</b></li><li><b>connectedCallback()</b> called on <b>child</b></li><li><b>errorCallback()</b> called on <b>parent</b></li><li><b>renderedCallback()</b> called on <b>child</b></li><li><b>renderedCallback()</b> called on <b>parent</b></li></ol><div><br /></div><div>Now it's time to get into action and verify whatever we specified above</div></div><h3 style="text-align: left;">constructor()</h3><div>This callback method is called as a component is constructed. The first statement inside a constructor should always be <b>super()</b>. Calling super() will provide the correct value for <b>this</b> keyword which we can use in our lwc. Some more important points about constructor are:</div><div><br /></div><div><ol style="text-align: left;"><li>You cannot access public properties (i.e. the properties annotated with @api) within a component's constructor. If you try to refer anything, it'll come as <b>undefined<br /><br /></b></li><li>You cannot access any html tag/element inside the component's constructor as the component is not rendered yet.<br /><br /></li><li>You can access private properties of a component inside a constructor</li></ol></div><div><br /></div><div>Now the question is: <b>What should we use constructor() for?</b></div><div><br /></div><div>Like a normal javascript class, this lwc class constructor can also be used to initialize some private variables you may have. It can be used to perform an operation like: calling your apex method (or any javascript method) to retrieve information or perform a particular action.</div><div><br /></div><div>Let's have a look at an example lightning component named <b>Child</b> below:</div><div><br /></div><h4 style="text-align: left;">child.js</h4><pre>import { LightningElement, api } from 'lwc';
export default class Child extends LightningElement {
count = 0;
@api
message = 'default';
constructor() {
super();
console.log('Child constructor called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
}</pre><div>As you can see in the above js file, a constructor is defined. Inside the constructor, first of all calling <b>super()</b>, then we are displaying a text - <b>Child constructor called</b>, then displaying value of private variable <b>count</b>, after that we're displaying value of public variable <b>message</b> and finally we're trying to display the reference to a <b>lightning-button</b> element present in our html.</div><div><br /></div><h4 style="text-align: left;">child.html</h4><div>We have a lightning-button with label <b>Increase Count</b> in our html file which will call a js method <b>increaseCount()</b> as the button is clicked:</div><pre><template>
<lightning-button label="Increase Count" onclick={increaseCount}></lightning-button>
<br /><br />
</template></pre><div><br /></div><div>We'll use this button later in the tutorial. For now, let's see the console logs as this component is rendered:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy3evBdLGiHXh_-pbbG-wgqkTIhcaxrW6Q3lIs9YW-TejEjEMQEw1oFR7Na4J5slVNwBUIQxchV5EP-WiEEVM1z9OYO9JdPgHGBqO24wRQaR3kLNBAj-9h7z4WvPLncH74sJDrVF2z9LZfv4vQf1qPSsxaBbu8FimF8hKiizeR7HVGiIyOyTrntep7eQg/s786/Screenshot%202023-08-06%20at%207.37.42%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="236" data-original-width="786" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy3evBdLGiHXh_-pbbG-wgqkTIhcaxrW6Q3lIs9YW-TejEjEMQEw1oFR7Na4J5slVNwBUIQxchV5EP-WiEEVM1z9OYO9JdPgHGBqO24wRQaR3kLNBAj-9h7z4WvPLncH74sJDrVF2z9LZfv4vQf1qPSsxaBbu8FimF8hKiizeR7HVGiIyOyTrntep7eQg/w640-h192/Screenshot%202023-08-06%20at%207.37.42%20PM.png" width="640" /></a></div><div><br /></div>First we have the message <b>Child constructor called</b>, the value of private variable <b>count</b> is coming as <b>0</b> inside the constructor, the value of public variable <b>message</b> is coming as <b>undefined</b> and finally, the reference to <b>lightning-button</b> is also coming as <b>null</b> inside the constructor, because the component is not rendered yet.<h3 style="text-align: left;">connectedCallback()</h3><div>This callback method is called when a lwc component is inserted into the dom. It establishes communication of the lwc component with the current document or container where it is inserted. Some important points about connected callback are:</div><div><br /></div><div><ol style="text-align: left;"><li>connectedCallback() on the parent component will be called before connectedCallback() on the child component is called. This is the reason that you cannot access child components from connectedCallback() of parent, as the child components are not inserted into the dom yet<br /><br /></li><li>This callback is invoked after all the public properties are set. This means that whatever initial values are being passed to the public properties of component, the component will have those values assigned when the connectedCallback() is called<br /><br /></li><li>This also means that connectedCallback() can only access the INITIAL VALUES OF PUBLIC PROPERTIES i.e. if you're updating the public property after the component is inserted, connectedCallback() will not be called again with the new value of public property. So, if you've a method which should be called based on the value of public property, it's better to call that method in a setter instead of connectedCallback() as the setter will be called again and again whenever the value of public property is set/updated<br /><br /></li><li>You can perform some initialization tasks in the connectedCallback() like: listening for events or getting some initial data from the server<br /><br /></li><li>connectedCallback() can be called more than 1 time as well. An example scenario can be: when a component is removed from the DOM and inserted again<br /><br /></li></ol></div><div>Let's add connectedCallback() to our Child lwc component now. The updated code is provided below:</div><div><br /></div><h4 style="text-align: left;">child.js</h4><pre>import { LightningElement, api } from 'lwc';
export default class Child extends LightningElement {
count = 0;
@api
message = 'default';
constructor() {
super();
console.log('Child constructor called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
connectedCallback() {
console.log('Child connected callback called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
}</pre>As you can see above, I'm trying to access the private property <b>count</b>, public property <b>message</b> and <b>lightning-button</b> again in my connectedCallback(), if you remember in our constructor, we were only able to access the value of private property i.e. count, message was coming as undefined and lightning-button reference was coming as null. Let's see if we're able to access anything else now out of these.<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjLF3bUe7j3ztTy1BgdlY59z87Fs_tBXbcYbNBY4DfwjvWfYdvjCcPNkcZlSpFeWgYIRLcg4xY_bt91POzaING3sWtPZ1gj3Qny9hLYZfgHHTAaVqV1Zvp1Ai7E8QWnHqHCh0ZSq3tXYJQ4Fi97G1SdvNrzc-N9PKlg9EYPRBrlkoKjJ5Z_rAaBoCdfbY/s790/Screenshot%202023-08-06%20at%206.47.41%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="240" data-original-width="790" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjLF3bUe7j3ztTy1BgdlY59z87Fs_tBXbcYbNBY4DfwjvWfYdvjCcPNkcZlSpFeWgYIRLcg4xY_bt91POzaING3sWtPZ1gj3Qny9hLYZfgHHTAaVqV1Zvp1Ai7E8QWnHqHCh0ZSq3tXYJQ4Fi97G1SdvNrzc-N9PKlg9EYPRBrlkoKjJ5Z_rAaBoCdfbY/w640-h194/Screenshot%202023-08-06%20at%206.47.41%20PM.png" width="640" /></a></div><div>As you can see in the output above, <b>message</b> is coming as <b>default</b> this time instead of undefined. However, the reference to lightning-button is still coming as null. This is because the public properties of our lwc component are now having the initial value assigned to them. As lightning-button is a child component with respect to our child lwc component, it's still not connected to the DOM and therefore is coming as null (remember the first point specified above: connectedCallback() on the parent is called before connectedCallback() on the child and here child component is lightning-button).</div><div><br /></div><div>Let's create a parent component as well and pass the value to our message (public property) from there to ensure it gets reflected in our connectedCallback() as well. Our parent component's name is <b>Parent</b> and the code for the same is provided below:</div><div><br /></div><h4 style="text-align: left;">parent.html</h4><div><pre><template>
<c-child message="hello"></c-child>
</template></pre>As you can see above, we're passing the value of message variable as <b>hello</b>. Let's take a look at the js file as well</div><div><br /></div><h4 style="text-align: left;">parent.js</h4><pre>import { LightningElement } from 'lwc';
export default class Parent extends LightningElement {
constructor() {
super();
console.log('Parent constructor called');
}
connectedCallback() {
console.log('Parent connected callback called');
console.log(this.template.querySelector('c-child'));
}
renderedCallback() {
console.log('Parent rendered callback called');
console.log(this.template.querySelector('c-child'));
}
}</pre><div>Here also, I've defined a constructor and a connectedCallback() as well to see in which order the parent child lifecycle hooks are being called. I also added a renderedCallback(). This renderedCallback() method should be called post connectedCallback() is called on child as per the order. We'll learn more about renderedCallback() in a bit, I've added this here for a reason. Let's have a look at the updated console.log() now:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgr1szb66hRnnpH16EZ6jEKLU5bnRo9FrGKn_02oHwb4EW9Scuic4x6nS8XV_0-nXz8VdPpUngxByjLTKVWogBJ7gNPH9V3alC6hUSzatcylPZLb8_18gzoGvupx6LGucTrEeataZqtkQqQ_pDMEWPVmeAUoY4HB-2O8XMy4efVWTuShqe1mSzDYJ4_GPk/s778/Screenshot%202023-08-12%20at%206.40.21%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="760" data-original-width="778" height="626" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgr1szb66hRnnpH16EZ6jEKLU5bnRo9FrGKn_02oHwb4EW9Scuic4x6nS8XV_0-nXz8VdPpUngxByjLTKVWogBJ7gNPH9V3alC6hUSzatcylPZLb8_18gzoGvupx6LGucTrEeataZqtkQqQ_pDMEWPVmeAUoY4HB-2O8XMy4efVWTuShqe1mSzDYJ4_GPk/w640-h626/Screenshot%202023-08-12%20at%206.40.21%20PM.png" width="640" /></a></div><div><br /></div>The order of events is shown below:<div><br /><div><ol style="text-align: left;"><li>Parent constructor() is called<br /><br /></li><li>Parent connectedCallback() is called and the reference to child component c-child is coming as null inside it<br /><br /></li><li>Child constructor() is called. Value of count is 0, message is undefined and reference to lightning-button is coming as null as it's further a child component for our child lwc.<br /><br /></li><li>Child connectedCallback() is called where value of count is 0. Notice that the value of message is hello instead of default this time because this is the value which is passed from our parent lwc to child lwc for this public property, reference to lightning-button is still coming as null<br /><br /></li><li>Finally, our renderedCallback() method is called in our parent component as per the order and as it's called after the child's connectedCallback() method, this means that the child lwc is now connected to the DOM. Therefore, reference to child lwc is not coming as null this time, as it was coming in the parent's connectedCallback() method.</li></ol><div><div><br /></div><div>I hope all of this is clear. Now, let's move on to the renderedCallback() method.</div><div><h3>renderedCallback()</h3><div>As the name suggests, this callback method is called once the component has rendered. As the component can render multiple times, this callback method will also be called each time the component is re-rendered. Some important points about renderedCallback() are:</div><div><br /></div><div><ol style="text-align: left;"><li>renderedCallback() on the child component is called before it's called on the parent component<br /><br /></li><li>Whenever the component renders, the expressions used in a template are re-evaluated. This means that if we've created a getter method which is used in our html file and that getter is returning a dynamic value based on some properties, it'll be re-evaluated as well<br /><br /></li><li>Whenever the component's state is changed, the component will be re-rendered<br /><br /></li><li>When a component is re-rendered, the lwc engine attempts to reuse the existing elements. For example: if you update something in the parent component due to which the parent re-renders, it'll not re-render the child component. Another example can be: if you're displaying a list of child components and if you re-order the list, then although the components are placed at different positions now, they're not re-rendered. The engine assumes that the same components are being used and just placed at a different positions now, so they're not re-rendered<br /><br />However, if you use a different key/update the key of child component, it might get re-rendered - I'm not going to show a demo of this, this is your homework. Try and let me know how it works in the comments down below!<br /><br /></li><li>As I specified in point 3, whenever the component's state is changed, the component will be re-rendered. Therefore, we need to make sure that we don't update the state of the component (for example: a property which is being displayed in component's html) in the renderedCallback() itself as it'll re-render the component and will call renderedCallback() again. In this case, the renderedCallback() will be called again and again recursively which will result in an infinite loop<br /></li></ol><div><br /></div><div>We've already defined renderedCallback() in our parent component as specified in the connectedCallback() section. Let's define renderedCallback() in our child lwc as well.</div></div><div><br /></div><h4 style="text-align: left;">child.js</h4><pre>import { LightningElement, api } from 'lwc';
export default class Child extends LightningElement {
count = 0;
@api
message = 'default';
constructor() {
super();
console.log('Child constructor called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
connectedCallback() {
console.log('Child connected callback called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
renderedCallback() {
console.log('Child rendered callback called');
console.log(this.template.querySelector('lightning-button'));
}
}</pre><div>Let's have a look at the updated logs now:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_XY3UMbWY96nN6ddtCxU364Uqe3t2J8ytDUEYSpxAB13bd1pM6n2lLmLREHxD8TElm-nDNA5TBi9jBIYiqK4Dzabl9dplmV6AwnpDqEPO370HL0A4ORW4nm7SemOc4aGxPWlq2VsbexwdMetWh6LXuiSqZsSUvZh3MQ1CS57nc-vZ-nkD8uda91aANq4/s940/Screenshot%202023-08-13%20at%2010.39.28%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="916" data-original-width="940" height="624" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_XY3UMbWY96nN6ddtCxU364Uqe3t2J8ytDUEYSpxAB13bd1pM6n2lLmLREHxD8TElm-nDNA5TBi9jBIYiqK4Dzabl9dplmV6AwnpDqEPO370HL0A4ORW4nm7SemOc4aGxPWlq2VsbexwdMetWh6LXuiSqZsSUvZh3MQ1CS57nc-vZ-nkD8uda91aANq4/w640-h624/Screenshot%202023-08-13%20at%2010.39.28%20AM.png" width="640" /></a></div><div><br /></div>The order of events is provided below:</div><div><br /></div><div><ol style="text-align: left;"><li>Parent constructor called<br /><br /></li><li>Parent connected callback called where reference to child lwc is coming as null<br /><br /></li><li>Child constructor called where count is 0, message is undefined and reference to lightning-button is coming as null<br /><br /></li><li>Child connected callback called where count is 0, message is hello and reference to lightning-button is again coming as null<br /><br /></li><li>Child rendered callback called where reference to lightning-button is coming properly as the lightning-button is connected to the DOM now<br /><br /></li><li>At last, parent rendered callback is called where reference to child lwc is coming properly as the child lwc is connected to DOM now</li></ol><div><br /></div><div>We are now going to re-render the parent lwc to see when renderedcallback() is called in parent and child LWCs. Let's add some more code!</div><div><br /></div><div>If you remember, our child lwc html is calling a method increaseCount() when the lightning-button is clicked, let's add that method to our child lwc js file as shown below:</div><pre>increaseCount() {
this.dispatchEvent(new CustomEvent('increasecount', {
detail: {
message: 'Increased count to ' + (++this.count)
}
}));
}</pre><div>This method will fire an event named <b>increasecount</b> whenever the button is clicked which will contain a message with the value of count variable increased by 1. The full code of child.js is provided below:</div><div><br /></div><h4 style="text-align: left;">child.js</h4><pre>import { LightningElement, api } from 'lwc';
export default class Child extends LightningElement {
count = 0;
@api
message = 'default';
constructor() {
super();
console.log('Child constructor called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
connectedCallback() {
console.log('Child connected callback called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
renderedCallback() {
console.log('Child rendered callback called');
console.log(this.template.querySelector('lightning-button'));
}
increaseCount() {
this.dispatchEvent(new CustomEvent('increasecount', {
detail: {
message: 'Increased count to ' + (++this.count)
}
}));
}
}</pre><div>Notice the increaseCount() added at the end. Let's update our parent.html file as well now:</div><div><br /></div><h4 style="text-align: left;">parent.html</h4><pre><template>
<lightning-card title={message}>
<p class="slds-var-p-around_small">
<c-child onincreasecount={updateMessage} message="hello"></c-child>
</p>
</lightning-card>
</template></pre><div>As you can see above, I've covered my child lwc with a lightning card which is displaying the value of <b>message</b> variable as <b>title</b>. I'm also capturing the <b>increasecount</b> event and calling another method in my parent.js named <b>updateMessage()</b> which will update the value of message variable displayed in the card title. Finally, let's take a look at our updateMessage() defined in parent.js as well:</div><pre>updateMessage(event) {
this.message = event.detail.message;
}</pre><div>As you can see above, it's updating the message variable with the value of message coming from the event. This message variable will be displayed as the title of lightning-card. Let's have a look at the full code below:</div><div><br /></div><h4 style="text-align: left;">parent.js</h4><pre>import { LightningElement, track } from 'lwc';
export default class Parent extends LightningElement {
message = 'Updated count will appear here!';
constructor() {
super();
console.log('Parent constructor called');
}
connectedCallback() {
console.log('Parent connected callback called');
console.log(this.template.querySelector('c-child'));
}
renderedCallback() {
console.log('Parent rendered callback called');
console.log(this.template.querySelector('c-child'));
}
updateMessage(event) {
this.message = event.detail.message;
}
}</pre><div>Notice the default value of <b>message</b> variable as: <b>Updated count will appear here!</b>. I've defined the updateMessage() method at the end which is updating the value of this message variable. Let's take a look at the component in action:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiixj0kq5hp5bcs0nQzGy2XYiUIH6yBYptXPjNeC-Qe7WV3vSZU7_5tKQ8rFk6W8Ki25CwM4wYs1DVd2VKs2H6UYyUAWM2f4XYhznVMLUghCLwTJUG7lEl6PsUT-QFdCJuZPnaDjiZ4oo7OkoNqAvXlMWFH_3Pe_AM5QVy840sqCfpZ0H_L0Tm5q6zwD0U/s460/CPT2308131134-460x134.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="134" data-original-width="460" height="116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiixj0kq5hp5bcs0nQzGy2XYiUIH6yBYptXPjNeC-Qe7WV3vSZU7_5tKQ8rFk6W8Ki25CwM4wYs1DVd2VKs2H6UYyUAWM2f4XYhznVMLUghCLwTJUG7lEl6PsUT-QFdCJuZPnaDjiZ4oo7OkoNqAvXlMWFH_3Pe_AM5QVy840sqCfpZ0H_L0Tm5q6zwD0U/w400-h116/CPT2308131134-460x134.gif" width="400" /></a></div><br /><div>As we click the <b>Increase Count</b> button present in child lwc, it fires an event with updated value of count. This <b>increasecount</b> event is captured by parent lwc and it'll update the value of <b>message</b> variable shown as a title of lightning-card as shown above.</div><div><br /></div><div>The thing to notice here is that, each time we click the button and the event is fired, it re-renders the parent component as shown below:</div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIZtHbIWw5PgXQF9ROz5eh_Fh5zIY57VLjSsVSGhr3z1CkVU_KhmMO3hihnFIJwQYhnFlSLYzT5d9hUnd4N5ZpczLbyAVpjx52kKfZj_zi8u2UjAeBDqPk7fSaHEHvgYwMs2TwEnt6s-M51qfx2C1poN-A8rsYbPLvMiAq_ZyR0MQ5jMMf24CDYKp2VF0/s1912/Screenshot%202023-08-13%20at%2011.53.28%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="946" data-original-width="1912" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIZtHbIWw5PgXQF9ROz5eh_Fh5zIY57VLjSsVSGhr3z1CkVU_KhmMO3hihnFIJwQYhnFlSLYzT5d9hUnd4N5ZpczLbyAVpjx52kKfZj_zi8u2UjAeBDqPk7fSaHEHvgYwMs2TwEnt6s-M51qfx2C1poN-A8rsYbPLvMiAq_ZyR0MQ5jMMf24CDYKp2VF0/w640-h316/Screenshot%202023-08-13%20at%2011.53.28%20AM.png" width="640" /></a></div><br /><div>Notice that only the parent lwc's rendered callback is called again and again and not the child one as I increased count from 1 to 5. This means that even though the parent is rendered multiple times, the child LWC is just reused as there's no change in the state of child lwc. It's still showing the same Increase Count button. This covers our point 3 and 4 under important points about renderedCallback(). It's time to move on to the next callback method now i.e. disconnectedCallback()</div><h3>disconnectedCallback()</h3><div>disconnectedCallback() will be called whenever the component is disconnected from the DOM, it's mainly useful to clean up the work done in connectedCallback(). You can use it for simple purposes like to remove cache or event listeners. Let's define disconnectedCallback() on our child component js. You can simply add the below method:</div><pre>disconnectedCallback() {
console.log('Child disconnected callback called');
}</pre><div style="text-align: left;"><br /></div><div style="text-align: left;">Our updated child.js file is shown below:</div><div style="text-align: left;"><br /></div><h4 style="text-align: left;">child.js</h4><pre>import { LightningElement, api } from 'lwc';
export default class Child extends LightningElement {
count = 0;
@api
message = 'default';
constructor() {
super();
console.log('Child constructor called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
connectedCallback() {
console.log('Child connected callback called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
renderedCallback() {
console.log('Child rendered callback called');
console.log(this.template.querySelector('lightning-button'));
}
disconnectedCallback() {
console.log('Child disconnected callback called');
}
increaseCount() {
this.dispatchEvent(new CustomEvent('increasecount', {
detail: {
message: 'Increased count to ' + (++this.count)
}
}));
}
}</pre><div>Notice the disconnectedCallback() added above the increaseCount() and below renderedCallback(). Let's update our parent component a little bit as well to make sure we're able to disconnect child lwc from the DOM.</div><div><br /></div><div>Updated parent.html file is provided below:</div><div><br /></div><h4 style="text-align: left;">parent.html</h4><pre><template>
<lightning-card title={message}>
<p class="slds-var-p-around_small">
<template if:true={show}>
<c-child onincreasecount={updateMessage} message="hello"></c-child>
</template>
<lightning-button label="Toggle Child" onclick={toggleChild}></lightning-button>
</p>
</lightning-card>
</template></pre><h4 style="text-align: left;"><span style="font-weight: 400;">As you can see above, I've added a template tag with </span><span>if:true<span style="font-weight: normal;"> condition which is checking a boolean variable named </span>show<span style="font-weight: normal;">. Only when this variable is true, our child component will be displayed to us. I'm going to create this variable in our parent.js file. I've added another lightning-button with label </span>Toggle Child<span style="font-weight: normal;"> which is calling the </span>toggleChild()<span style="font-weight: normal;"> when clicked. On click of this button, I'm going to toggle the value of show variable from true -> false or from false -> true which will hide/show the child lwc component. This will utlimately call our disconnectedCallback() on our child lwc as well. Let's take a look at the updated parent.js now:</span></span></h4><h4 style="text-align: left;">parent.js</h4><div><pre>import { LightningElement, track } from 'lwc';
export default class Parent extends LightningElement {
message = 'Updated count will appear here!';
show = true;
constructor() {
super();
console.log('Parent constructor called');
}
connectedCallback() {
console.log('Parent connected callback called');
console.log(this.template.querySelector('c-child'));
}
renderedCallback() {
console.log('Parent rendered callback called');
console.log(this.template.querySelector('c-child'));
}
updateMessage(event) {
this.message = event.detail.message;
}
toggleChild() {
this.show = !this.show;
}
}</pre><span><span style="font-weight: normal;">As you can see above, I've added </span><span><b>show</b></span><span style="font-weight: normal;"> variable below message variable whose default value is true. I've also added another method named </span><span><b>toggleChild()</b> at the end. This method will be called when we click the <b>Toggle Child</b> lightning button and it'll toggle the value of <b>show</b> variable from true to false and from false to true.</span></span></div><div><span><span><br /></span></span></div><div><span><span>This toggling will automatically hide/show the child lwc or I can say connect/disconnect child lwc from the DOM. Let's take a look at the component in action first:</span></span></div><div><span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkN5gO-BZzZKqjGdxsLrZfOzxXDf8xhmTdHIU903cJQdeUB2do4kerzeLfity6q9J4kZeDmNqraM8Iq5m3ffRiT5ffVmLEUYCkOa17z4dwxkcJ7t95enAtLKU1OzyzBkPwjH8jyglrT4As4Eu9SHnp7JsCN2JEGpqD0yQG9oUw18vq9ohAiP-USaDQyHo/s460/CPT2308131359-460x166.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="166" data-original-width="460" height="144" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkN5gO-BZzZKqjGdxsLrZfOzxXDf8xhmTdHIU903cJQdeUB2do4kerzeLfity6q9J4kZeDmNqraM8Iq5m3ffRiT5ffVmLEUYCkOa17z4dwxkcJ7t95enAtLKU1OzyzBkPwjH8jyglrT4As4Eu9SHnp7JsCN2JEGpqD0yQG9oUw18vq9ohAiP-USaDQyHo/w400-h144/CPT2308131359-460x166.gif" width="400" /></a></div>As you can see in the above demo, first of all I increased the count using Increase Count lightning-button in the child component to 2. Then I clicked on Toggle Child button which removed the child component from the DOM. I brought it back by clicking the Toggle Child button again and then I clicked on Increase Count button again which increased the value of count starting from 1 to 5. It started from 1 again as the child lwc is reinitialized and therefore is having the default value of count as 0. Let's take a look at related logs now.</span></div><div><span><br /></span></div><div><span>After the components were loaded initially and I clicked on <b>Increase Count</b> button twice and then the <b>Toggle Child</b> button which removed the child lwc from DOM. The console.log statements for these 3 operations are shown below:</span></div><div><span><br /></span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNUXDKNgvV2MsWi1cBkJvlQTOKzPxr1-AE4MXU-vSdygXqVJkqavYIgbBwx5NmT7zG7kJ3qO-YXtDcyL7sS-nENYFPdI2WUEt72X6sweXNMxHHvB2AODJ4zpZqJZ_zlLFgz30U9V35VrW318xR_QsYmy46ExCaExR6xaAZgWeSm_tagZKcIzfuDb8IJYU/s1918/Screenshot%202023-08-13%20at%202.03.48%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="718" data-original-width="1918" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNUXDKNgvV2MsWi1cBkJvlQTOKzPxr1-AE4MXU-vSdygXqVJkqavYIgbBwx5NmT7zG7kJ3qO-YXtDcyL7sS-nENYFPdI2WUEt72X6sweXNMxHHvB2AODJ4zpZqJZ_zlLFgz30U9V35VrW318xR_QsYmy46ExCaExR6xaAZgWeSm_tagZKcIzfuDb8IJYU/w640-h240/Screenshot%202023-08-13%20at%202.03.48%20PM.png" width="640" /></a></div></div><div><span><br /></span></div><div><span>As you can see, for the first two operations, when count is increased, parent renderedCallback() is called and I can refer the child lwc easily as it's connected to the DOM. Then I clicked <b>Toggle Child</b> button, it called child's disconnectedCallback() and we have the statement: <b>Child disconnected callback called</b> printed to the console. It also called parent's renderedCallback() as the child is removed from the DOM so the parent is also re-rendered. Notice that this time, the child lwc reference in the parent's renderedCallback() is coming as null as the child component is no more connected to the DOM.<br /></span></div><div><span><br /></span></div><div><span>Let's click the Toggle Child button again now:</span></div><div><span><br /></span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR-G0699dVioRg5N8VUbvcsl9cLi_dJoZZunK3beqMs9Be6jufceQltE59Do6XwDuaRFzgXdBq6PNiJwYeI_iXWfL5VjhvpToi-Szks6nYYTKeT8aj8EPL1Ubw8CUpfC8Ag4bW1UEkaXmoePtPih9BT4F3aypKYcuDWperYKxY0OfZa-QPfPf7qs8z3mU/s1410/Screenshot%202023-08-13%20at%203.34.14%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1016" data-original-width="1410" height="462" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgR-G0699dVioRg5N8VUbvcsl9cLi_dJoZZunK3beqMs9Be6jufceQltE59Do6XwDuaRFzgXdBq6PNiJwYeI_iXWfL5VjhvpToi-Szks6nYYTKeT8aj8EPL1Ubw8CUpfC8Ag4bW1UEkaXmoePtPih9BT4F3aypKYcuDWperYKxY0OfZa-QPfPf7qs8z3mU/w640-h462/Screenshot%202023-08-13%20at%203.34.14%20PM.png" width="640" /></a></div><div><br /></div>As you can see above, as I clicked on Toggle Child button again, the child lwc is again connected to DOM. Therefore, the child constructor() is called again, then child connectedCallback() is called, then renderedCallback() and finally parent's renderedCallback() is called once again and this time the reference to child lwc is not coming as null.</div><div><br /></div><div>Post that, I clicked on Increase Count button 5 more times, the count is increased from 1 to 5 and the parent lwc is rendered 5 times as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWjw2-dDgardCY81wtLWeTGxUyR4n3fm8oVwfqORanpEh40JYc_IWtFMhhn5SohNirYItx-Pr0wTOJUYWze367EDWgJs5TVC5O9KYUQuz7hE5w7Aq7PNhA8HSLI8l8-uZqI0cnUPNb6Sr2amBS2UF661tn9sDhadYaS2rSweqq9gNuoGA3k78if01SDhU/s1478/Screenshot%202023-08-13%20at%203.42.37%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1112" data-original-width="1478" height="482" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWjw2-dDgardCY81wtLWeTGxUyR4n3fm8oVwfqORanpEh40JYc_IWtFMhhn5SohNirYItx-Pr0wTOJUYWze367EDWgJs5TVC5O9KYUQuz7hE5w7Aq7PNhA8HSLI8l8-uZqI0cnUPNb6Sr2amBS2UF661tn9sDhadYaS2rSweqq9gNuoGA3k78if01SDhU/w640-h482/Screenshot%202023-08-13%20at%203.42.37%20PM.png" width="640" /></a></div><h3>errorCallback()</h3><div>Now, let's take a look at our last method in the lwc lifecycle i.e. errorCallback(). This callback method will be called whenever an error occurs in lifecycle hook and it captures errors in all the child (descendent) components in it's tree. Let's understand with an example. I'm going to throw error from the connectedCallback() of my <b>child.js</b> file. I'll also define errorCallback() methods in both child and parent lwc to understand which method is being called and the information received in the errorCallback() method. Let's update our child lwc first.</div></div></div></div></div><div><br /></div><h4 style="text-align: left;">child.js</h4><pre>import { LightningElement, api } from 'lwc';
export default class Child extends LightningElement {
count = 0;
@api
message = 'default';
constructor() {
super();
console.log('Child constructor called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
}
connectedCallback() {
console.log('Child connected callback called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
let error = {
code: 100,
message: 'Error from child connected callback!'
};
throw error;
}
renderedCallback() {
console.log('Child rendered callback called');
console.log(this.template.querySelector('lightning-button'));
}
disconnectedCallback() {
console.log('Child disconnected callback called');
}
errorCallback(error, stack) {
console.log('Child error callback called, error = ' + JSON.stringify(error) + ', stack = ' + JSON.stringify(stack));
}
increaseCount() {
this.dispatchEvent(new CustomEvent('increasecount', {
detail: {
message: 'Increased count to ' + (++this.count)
}
}));
}
}</pre><div>As you can see above, I've updated the connectedCallback(). I'm also showing this update again below:</div><pre>connectedCallback() {
console.log('Child connected callback called');
console.log(this.count);
console.log(this.message);
console.log(this.template.querySelector('lightning-button'));
let error = {
code: 100,
message: 'Error from child connected callback!'
};
throw error;
}</pre><div>I've added 4 more lines after console.log statements where I'm defining an error object with two properties, code and message. Then I'm throwing that error object. I also defined errorCallback() method as shown in the below snippet:</div><pre>errorCallback(error, stack) {
console.log('Child error callback called, error = ' + JSON.stringify(error) + ', stack = ' + JSON.stringify(stack));
}</pre><div><br /></div><div>errorCallback() has two parameters:</div><div><ol style="text-align: left;"><li><b>error</b>: This is the JavaScript native error object. It's the error which was thrown by component where it occured. In our case it'll be the error object we're throwing which is having two properties: code and message.<br /><br /></li><li><b>stack</b>: This is a string specifying - in which component the error occured. It'll show path from the component whose errorCallback() was called till the child component where error was thrown<br /></li></ol><div><br /></div><div>Let's add the errorCallback() in parent lwc as well. I'm going to add the below method to parent.js:</div></div><pre>errorCallback(error, stack) {
console.log('Parent error callback called, error = ' + JSON.stringify(error) + ', stack = ' + stack);
console.log(this.template.querySelector('c-child'));
}</pre><div>Let's take a look at the full parent.js file as well after updates:</div><div><br /></div><h4 style="text-align: left;">parent.js</h4><pre>import { LightningElement, track } from 'lwc';
export default class Parent extends LightningElement {
message = 'Updated count will appear here!';
show = true;
constructor() {
super();
console.log('Parent constructor called');
}
connectedCallback() {
console.log('Parent connected callback called');
console.log(this.template.querySelector('c-child'));
}
renderedCallback() {
console.log('Parent rendered callback called');
console.log(this.template.querySelector('c-child'));
}
errorCallback(error, stack) {
console.log('Parent error callback called, error = ' + JSON.stringify(error) + ', stack = ' + stack);
console.log(this.template.querySelector('c-child'));
}
updateMessage(event) {
this.message = event.detail.message;
}
toggleChild() {
this.show = !this.show;
}
}</pre><div>Notice the errorCallback() added below renderedCallback(). Now, let's take a look at the console statements as the components are loaded to understand how errorCallback() is being called:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbPh9OvJAVJ_H6RpjkjXgu7riThud0dwPlB-Dq1D3xEnlJqBkTSAC33z8OH3Ct940fGyn_x3QhY2T_1V-XSK8c0HEr4xeN15AMXD3351yJBolDgGDI3TrlZds5hbL411_rR4qRNqtIqbh56xCUxoW9nQPUFP_3BzNWHKEtOIIHlPcMWd1wXQafhCDFUXU/s1170/Screenshot%202023-08-14%20at%207.57.29%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1166" data-original-width="1170" height="638" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbPh9OvJAVJ_H6RpjkjXgu7riThud0dwPlB-Dq1D3xEnlJqBkTSAC33z8OH3Ct940fGyn_x3QhY2T_1V-XSK8c0HEr4xeN15AMXD3351yJBolDgGDI3TrlZds5hbL411_rR4qRNqtIqbh56xCUxoW9nQPUFP_3BzNWHKEtOIIHlPcMWd1wXQafhCDFUXU/w640-h638/Screenshot%202023-08-14%20at%207.57.29%20PM.png" width="640" /></a></div><div><br /></div><div>A couple of things to notice above:</div><div><br /></div><div><ol style="text-align: left;"><li>Only the parent errorCallback() is called and not the errorCallback() present in the child lwc<br /><br /></li><li>Error object is received in the errorCallback() which is exactly the same as thrown by the child lwc. The stack string received in the errorCallback() is showing the stack/path from the parent lwc (the component whose errorCallback() is called) to child lwc (where the error was thrown) as: <b><c-parent> <c-child></b><br /><br /></li><li>I am trying to display a reference to child lwc in the errorCallback() as well and it's working fine. This means that once the child lwc is connected to the DOM it can be referred in any of the callback methods be it errorCallback() or renderedCallback()</li></ol><div><br /></div><div>Let's take a look at the order in which the callback methods are executed as well:</div></div><div><br /></div><div><ol style="text-align: left;"><li>Parent: constructor() called<br /><br /></li><li>Parent: connectedCallback() called (reference to child lwc is null)<br /><br /></li><li>Child: constructor() called (count is coming as 0, message as undefined and reference to lightning-button is coming as null)<br /><br /></li><li>Child: connectedCallback() called (count is coming as 0, message as hello and reference to lightning-button is still null as lightning-button is not connected to DOM yet). This callback method is also throwing error now<br /><br /></li><li>Parent: errorCallback() called (child lwc can now be referenced as it's now connected to DOM)<br /><br /></li><li>Child: renderedCallback() called (reference to lightning-button is coming properly now as lightning-button is now connected to DOM)<br /><br /></li><li>Parent: renderedCallback() called (child lwc can now be referenced here as well because it's now connected to DOM)</li></ol><div><br /></div><div>So that's the final series of events/callbacks we have for this post in our demo components.</div></div><div><br /></div><div>We covered all the callback methods/lifecycle hooks of lwc in this post.</div><div><br /></div><div><div>That's all for this tutorial everyone, I hope you liked it. Let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com1tag:blogger.com,1999:blog-8643239839712475364.post-34367100129666377772023-02-24T23:58:00.002-08:002023-02-24T23:58:43.850-08:00Child to Parent communication using LWC Events | LWC Tutorial | Lightning Events in LWCHello Trailblazers, <div><br /></div><div>I recently posted the below video on <a href="https://www.youtube.com/@sfdcstop" target="_blank"><span style="color: #2b00fe;">SFDC Stop YouTube Channel</span></a> where we learned how can we communicate from a child lwc to a parent lwc using events.</div><div><br /><h2 style="text-align: left;">Tutorial Video</h2><p style="-webkit-text-stroke-width: 0px; font-size: medium; font-weight: 400;"><iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="400" src="https://www.youtube.com/embed/hfhGsgaAIQY" width="100%"></iframe></p>In this post, I'm going to share the code snippet we used in the above tutorial with a brief explanation of the same. You can watch the small ~9min video, I shared above to learn the concept in detail.<div><br /></div><div>Let's have a look at the code snippets now!</div><div><br /></div><h2 style="text-align: left;">Child LWC</h2><div>First of all, we created a child lwc. This component will fire the event on a button click, which will be handled by the parent lwc. Let's have a look at the HTML and Js code of our child lwc one by one:</div><h3 style="text-align: left;">child.html</h3><pre><template>
<lightning-button label="Increase Count" onclick={increaseCount}></lightning-button>
</template></pre><div>As you can see above, we defined a lightning button with label <b>Increase Count</b>, this button will call the js method <b>increaseCount()</b> which will increase the value of a counter we'll define in our js and fire an event. The parent LWC will capture the event and will display the value of this counter along with some text received in the event body.</div><h3 style="text-align: left;">child.js</h3><pre>import { LightningElement } from 'lwc';
export default class Child extends LightningElement {
count = 0;
increaseCount() {
this.dispatchEvent(new CustomEvent('increasecount', {
detail: {
message: 'Increased count to ' + (++this.count)
}
}));
}
}</pre><div>As you can see above, we defined a variable <b>count</b> whose initial value is 0. As we click the <b>Increase Count</b> button, this <b>increaseCount()</b> will be called. It'll dispatch a new event named <b>increasecount</b> and in the body of this event (which is an object), we defined a property named <b>detail</b>. Now, in this detail property, we can pass anything, it can be a string, an array, an integer, an object...anything.</div><div><br /></div><div>For now, in the detail of this event, we're passing an object, which has a single property named <b>message</b> and the value of message is: <b>Increased count to <increased value of count variable></b>. This means, each time this method is called, count variable will be incremented by 1 and the string message will be passed in event detail which has the updated count. For example, when the first time, this method is called, we will have the message <b>Increased count to 1</b> in the event detail. Similarly, the second time this method is called when the button is clicked again, count variable will increase again by 1 and the message: <b>Increased count to 2</b> will be passed in the event detail.</div><div><br /></div><div>Our parent lwc will accept this event and will display the message. Let's have a look at that now!</div><div><br /></div><h2 style="text-align: left;">Parent LWC</h2><div>Let's start by looking at the html part again:</div><h3 style="text-align: left;">parent.html</h3><pre><template>
<lightning-card title={message}>
<p class="slds-var-p-around_small">
<c-child onincreasecount={updateMessage}></c-child>
</p>
</lightning-card>
</template></pre><div>In this component, first of all we defined a lightning-card with <b>title</b> equal to the <b>message</b> variable that we'll define in our js. Then, for the card body, we defined a paragraph with a small padding and within that paragraph, we called our child component. Now, we know that our child lwc will fire <b>increasecount</b> event when the button is clicked, so we're handling the same event as: <b>onincreasecount={updateMessage}</b>. This means: whenever this <b>increasecount</b> event is fired, it'll call the <b>updateMessage()</b> method defined in the js of our parent lwc.</div><div><br /></div><div>For every event which is fired by a child lwc, you can handle it by adding a prefix <b>on</b> before it's name and then binding it to a method defined in your js. For example: here our event name is <b>increasecount</b> so we added the <b>on</b> prefix before event name and it became: <b>onincreasecount</b> and we binded it to our <b>updateMessage()</b> method defined in our js. This updateMessage() method will receive the same event that we fired from our child component. Let's checkout the js to understand how we're handling this event.</div><h3 style="text-align: left;">parent.js</h3><pre>import { LightningElement } from 'lwc';
export default class Parent extends LightningElement {
message = 'Updated count will appear here!';
updateMessage(event) {
this.message = event.detail.message;
}
}</pre><div>As you can see in the above code snippet, we defined a <b>message</b> variable in our js. This is the same variable which is used as the value of <b>title</b> in our lightning card. The default value of this variable is <b>Updated count will appear here!</b> so by default the lightning card will display this message as you can see in the below screenshot:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhds1fOxOJgzsamKfEgcDqF2vupcT49RaegSw6I0Z3Xkl4uzHDlG26g_L4KLGHFNxP4FwfumZguk9H5Q2SEC-wa0Fft-q-QYHFcT54hTy_JQ_2usAinnnEQPbRFwftyeginkJujq54dmy2qyQBw6vLB-TVcVZY4uDWZMyRzwhYw5DjDSwjhrf-e4V1E/s958/Screenshot%202023-02-25%20at%2011.53.03%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="958" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhds1fOxOJgzsamKfEgcDqF2vupcT49RaegSw6I0Z3Xkl4uzHDlG26g_L4KLGHFNxP4FwfumZguk9H5Q2SEC-wa0Fft-q-QYHFcT54hTy_JQ_2usAinnnEQPbRFwftyeginkJujq54dmy2qyQBw6vLB-TVcVZY4uDWZMyRzwhYw5DjDSwjhrf-e4V1E/w640-h186/Screenshot%202023-02-25%20at%2011.53.03%20AM.png" width="640" /></a></div><div><br /></div><div>We also defined a method <b>updateMessage(event)</b>, this method will receive the same event which is fired by our child lwc in the parameter and will update the <b>message</b> variable with the value that is coming from the message property of our event detail object. Remember, we defined an object in the detail property of our event body, with a single property named: <b>message</b> whose value was <b>Increased count to <increased value of count variable></b>? So, when we do: <b>this.message = event.detail.message;</b> we're basically saying, get the object defined in detail property of event body (event.detail) and then get the value of message property from that object (event.detail.message). We're assigning the value of this message property from event detail object to our message variable and this message variable is displayed as the title of our lightning card.</div><h3 style="text-align: left;">parent.js-meta.xml</h3>I'm going to add this component to my homepage for the demo, so I've added a target named <b>lightning__HomePage</b> as shown below:<pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre><br /><h2 style="text-align: left;">Demo</h2><div>Now, as we click on the <b>Increase Count</b> button once, the child component's counter (count variable) will increment to <b>1</b>, the value of message will be <b>Increased count to 1</b>, the lightning card title will update and the output for the same is shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIC4ha2iZ7O1THVmG5MnML2RKCcC9ByxpYjAa7D1zuauUOcZVHCRbkI7gWwvudMVz44ZUdH3HkWYnSgLZJlBvKH5wNpYT16PWVPJaPIcoNGAGi6Ql89jAj9MhvozWrYyQM2SFRHDEHAM_qGOPuVdQkSzMXKxESt06GHdeAxGRb9Ud5NtLQJthDEYb_/s950/Screenshot%202023-02-25%20at%2012.38.20%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="258" data-original-width="950" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIC4ha2iZ7O1THVmG5MnML2RKCcC9ByxpYjAa7D1zuauUOcZVHCRbkI7gWwvudMVz44ZUdH3HkWYnSgLZJlBvKH5wNpYT16PWVPJaPIcoNGAGi6Ql89jAj9MhvozWrYyQM2SFRHDEHAM_qGOPuVdQkSzMXKxESt06GHdeAxGRb9Ud5NtLQJthDEYb_/w640-h174/Screenshot%202023-02-25%20at%2012.38.20%20PM.png" width="640" /></a></div><div><br /></div>Similarly, as we click on this button again, the child component's count variable will update to <b>2</b>, the message passed through the event will be <b>Increased count to 2</b> and the same message will be displayed in the title of our lightning card as shown below:<div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD48wDQjnx4snZdPTBNuX8I0Vw0sa_xrSM5heO-PIaz10hA3UTr0FnPQ_dOrhLaEGexQZvPK3AOzpC1U4HYZizsu_qOKN9Tj74YOwU_vUdLy4uIfFwn2P4xGRO4MTC2Srhl8r19TsZxen6q9RHAKdhoE1l-xn4kQW8CQ7MUD0wa2yqeB3wdUPta9Gj/s948/Screenshot%202023-02-25%20at%2012.40.50%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="258" data-original-width="948" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD48wDQjnx4snZdPTBNuX8I0Vw0sa_xrSM5heO-PIaz10hA3UTr0FnPQ_dOrhLaEGexQZvPK3AOzpC1U4HYZizsu_qOKN9Tj74YOwU_vUdLy4uIfFwn2P4xGRO4MTC2Srhl8r19TsZxen6q9RHAKdhoE1l-xn4kQW8CQ7MUD0wa2yqeB3wdUPta9Gj/w640-h174/Screenshot%202023-02-25%20at%2012.40.50%20PM.png" width="640" /></a></div><br /><div>I'm sharing a small screen recording below so that it's clear how the component is behaving in real time:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlr6QWwi3Ug7KHRBrcmY4ODqlPJv_Yz--oQ_gzxVgR_q1RZwAuXzuI4vTiO04XeJUC6l-3CLw2eULTC-EkGSIv07Fhxq961mq6AfgWy6vkgmXS4OP4HV01xaLpxscxACZAQlDt2ZobTZTK6Gh2PDiLmVonXSV45vLmTm0kDi4r_aNSjhrxfbl7OOM0/s388/CPT2302251253-388x135.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="135" data-original-width="388" height="139" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlr6QWwi3Ug7KHRBrcmY4ODqlPJv_Yz--oQ_gzxVgR_q1RZwAuXzuI4vTiO04XeJUC6l-3CLw2eULTC-EkGSIv07Fhxq961mq6AfgWy6vkgmXS4OP4HV01xaLpxscxACZAQlDt2ZobTZTK6Gh2PDiLmVonXSV45vLmTm0kDi4r_aNSjhrxfbl7OOM0/w400-h139/CPT2302251253-388x135.gif" width="400" /></a></div><br /><div>That's all for this tutorial everyone, I hope you liked it. Let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-2052272064592110002022-12-10T01:50:00.004-08:002022-12-10T01:50:20.113-08:00Introduction to Platform Events in SalesforceHello Trailblazers,<div><br /></div><div>In this post we're going to learn about platform events. Platform Events are a part of salesforce's enterprise messaging platform. In short, we can say that a platform event is an event using which applications can communicate with each other inside and outside of salesforce. </div><div><br /></div><div>Therefore, it can be used to send messages/notifications from one app to another within salesforce and also we can send messages to other applications which are outside of salesforce, thereby using it as a means to integrate salesforce with other systems.</div><div><br /></div><h2 style="text-align: left;">What is an Event-Driven Software Architecture?</h2><div>In order to understand platform events, we should know what an event-driven software architecture looks like. It's described very well in the <a href="https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_intro_architecture.htm" target="_blank"><span style="color: #2b00fe;">original salesforce documentation</span></a> in detail. We're going to focus on some of the important stuff here.</div><div><br /></div><div>An event driven architecture consist of an <b>Event Bus</b> i.e. the service which ensures the delivery of the event in the correct order 🚌. It consist of multiple <b>Event Channels</b> through which the event travel. We also have an <b>Event Producer</b> i.e. the application which will fire the event and an <b>Event Consumer</b> i.e. the application which will receive the event. Each event will have an <b>Event Message</b> which is the actual information the event is carrying. And <b>That's All</b>. As simple as that!</div><div><br /></div><h3 style="text-align: left;">Why and When should I use platform events?</h3><div>When a system/application request salesforce for the latest information of some records let's say by hitting the API, there is a separate connection established between the requestor application and salesforce. </div><div><br /></div><div>Let's consider a hypothetical scenario where you are creating/updating leads. When a condition is fulfilled (let's say personal info is updated) you need all the updated lead information to be synced with 5-10 external systems in <b>near-real time</b>. </div><div><br /></div><div>Now, for each such system (requestor application) there will be a separate connection to salesforce maybe via REST API (or any other mechanism) and this connection also depends on the availability of the service. Let's say if the service (i.e. salesforce api if the external system is requesting information) is not available for some time (maybe a few seconds) due to any reason, the requests to get the latest information will fail for some of the systems while the other systems will remain up to date. This leads to <b>Inconsistency</b> between receiver systems.</div><div><br /></div><div>So, in this case, we can say:</div><div><br /></div><div><ol style="text-align: left;"><li>The availability of the service is critical<br /><br /></li><li>To add (integrate with) a new system, we need to establish a separate connection</li></ol></div><div><br /></div><div>However, if we have an <b>event based model</b>, Salesforce (Event Producer) can fire a platform event - each time the lead record is created/updated and it's satisfying the condition. It's not necessary for salesforce to know who's consuming the event, there can be 5, 10, 15, 20..... consumers who would like to receive the same information. Even if an external system is not available for some time, the events fired will still remain in the event bus and can be received/consumed once the system is available again. If salesforce (producer system) is not available, it can fire the events once it's back and publish it to the event bus.</div><div><br /></div><div>Therefore, we can say:</div><div><br /></div><div><ol style="text-align: left;"><li>The availability of service is not critical. If the producer is not available, it's fine as it'll publish the events to the bus when it's available again and similarly if the consumer is not available for some time, it can also retrieve the events from the same event bus when it's back<br /><br /></li><li>Adding/integrating a new system is fairly easy as we don't need to establish a separate connection to the service (event producer). The new consumer can simply connect to the event bus and can start receiving events<br /><br /></li><li>The only consideration/dependency between event producer and consumer here is the format of the event message content as it'll be the same for all consumers</li></ol><div><br /></div><div><b>Note:</b> Each platform event message consist of a <b>ReplayId</b> using which we can identify the event. We can also replay the stream of events after a particular event using the replay id.</div></div><div><br /></div><h2 style="text-align: left;">Types of Platform Events</h2><div>There are two types of platform events:</div><div><br /></div><div><ol style="text-align: left;"><li>Standard Platform Events<br /><br /></li><li>Custom Platform Events<br /><br /></li></ol></div><h3 style="text-align: left;">Standard Platform Events</h3><div>Standard platform events consist of some limited events that are pre-defined by salesforce. Most of these events are published in response to an action that occured in the org or to report errors. A good example of a standard platform event is <b>BatchApexErrorEvent</b> which can be fired by our batch class to report any errors that are encountered. You can implement the <b>Database.RaisesPlatformEvents</b> interface in your batch class to fire these events and capture them in your event subscriber to log details of the errors that are encountered.</div><div><br /></div><div>A full list of standard platform events is available <a href="https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_objects_list.htm" target="_blank"><span style="color: #2b00fe;">here</span></a>. Apart from this list, the <a href="https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_objects_change_data_capture.htm" target="_blank"><span style="color: #2b00fe;">Change Data Capture Events</span></a> (CDC) also comes under standard platform events and can be used to notify when a record is created, updated, deleted OR undeleted from recycle bin. We'll need a separate blog post to cover CDC in detail, let me know in the comments down below if you're interested in the same.</div><div><br /></div><h3 style="text-align: left;">Custom Platform Events</h3><div>Custom Platform Events are user-defined events that can be used to convey any specific information that you would like to publish using platform events. Defining a custom platform event is very similar to defining a custom object in salesforce. You can specify the name of the platform event, the field names and their types. There are also some limitations if we compare it to a salesforce objects:</div><div><br /></div><div><ul style="text-align: left;"><li>These event records cannot be viewed in salesforce directly so we don't have a page layout for them.<br /><br /></li><li>Out of the CRUD operation, only CREATE and READ are valid for platform events. You CANNOT UPDATE or DELETE a platform event record.<br /><br /></li><li>Published event messages cannot be rolled back<br /><br /></li><li>The type of fields for platform event objects are limited. Only Checkbox, Date, Date/Time, Number, Text and Text Area (Long) fields are supported</li></ul><div><br /></div><div>All the above points, kind of makes sense as well, because once you fire a platform event record and it's published to the event bus, now it can be read by any subscriber so there is no point of updating or deleting an event which is already published. Also, rollbacking a platform event is also risky because the publisher is not aware of the subscribers, so we can't say which subscriber has already consumed it and which hasn't. Lastly, we don't really need fields like: lookup, master-detail and other field types in platform events which we have in custom objects.</div></div><div><br /></div><h2 style="text-align: left;">Publishing Platform Events</h2><div>We have two options for publishing platform events:</div><div><br /></div><div><ol style="text-align: left;"><li><b>Publish Immediately</b> - Platform events are published as they're fired. They won't wait for the current transaction to complete successfully. A good use case can be if you want to create error log (custom object to store errors) records when an exception is thrown. You can fire a platform event with the error message and it's published immediately. Therefore, an error log record will be created when this platform event is received (a separate transaction) even when the actual transaction is rolled back because of an exception.<br /><br /></li><li><b>Publish After Commit</b> - Platform events are published only after a transaction commits successfully. Let's say you want to send an email when something happens, so you want to be sure that the transaction is completed successfully before you fire a platform event which is subscribed by a flow (let's say) and it sends an email. In this case, Publish After Commit is a better option.</li></ol></div><div><br /></div><div>Before we wrap up this post, I would like to tell you about some more important points about platform events:</div><div><br /></div><div><ol style="text-align: left;"><li>Platform Events can be <b>High Volume</b> or <b>Standard Volume</b>. In API version 45.0 and later, all new custom platform events are high volume by default, so we'll focus mainly on that. A big difference between high volume and standard volume platform events is: High Volume platform events are stored in the event bus for <b>3 days</b> whereas Standard Volume platform events are stored in the event bus for <b>1 day</b>. The higher retention time makes the integration more robust as the system can be down for some time (due to any issue), can be up again and receive the event messages it lost when it was down<br /><br /></li><li>The order in which the events are published remains the same - as we specify at the time of publishing these events, when you're publishing multiple events in the same publish call. If you're publishing multiple events separately using different calls to publish(), the order is not guaranteed.<br /><br /></li><li>Platform Events are suffixed with __e<br /><br /></li><li>Each platform event message has a replay id using which it can be replayed from the event bus.<br /><br /></li><li>Some important terms include: Event Bus, Event Channel, Event Producer, Event Consumer and Event Message (we discussed all of these above in the Event-Driven software architecture section)</li></ol></div><div><br /></div><div>Ok, enough of theory and this was the only theory I wanted to share before we deep dive into practical stuff - actually fire and subscribe to platform events which we'll do in the similar upcoming posts.</div><div><br /></div><div><div>That's all for this tutorial. I hope you liked it, let me know your feedback in the comments down below. You can connect with my on <a href="https://bit.ly/downloadconnectionsapp" target="_blank"><span style="color: #2b00fe;">Connections</span></a> app by scanning the QR code given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguTjIp8eWnSbConcRmKqCoNQwh0sVicj1hoBMd-WbUu7MeKb4N-cCoT2XhLDt1pwJDDKrgMDV3O2mgEJ7KpZNqs8lYlJ3VqshQcwEc1XyL5oTGVaePgwHyB8NH-XmdQ62qcIYNuGohqTXMa0Y-nihQ3aPhkYHHFcPz_v__JysweglemNKHVBgMR6o3/s1208/Screenshot%202022-11-26%20at%2012.00.35%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1008" data-original-width="1208" height="267" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguTjIp8eWnSbConcRmKqCoNQwh0sVicj1hoBMd-WbUu7MeKb4N-cCoT2XhLDt1pwJDDKrgMDV3O2mgEJ7KpZNqs8lYlJ3VqshQcwEc1XyL5oTGVaePgwHyB8NH-XmdQ62qcIYNuGohqTXMa0Y-nihQ3aPhkYHHFcPz_v__JysweglemNKHVBgMR6o3/s320/Screenshot%202022-11-26%20at%2012.00.35%20PM.png" width="320" /></a></div><div>Or search in the code scanner screen using my username: <b>rahulmalhotra</b></div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-79070057532278661362022-11-25T23:06:00.000-08:002022-11-25T23:06:03.321-08:00Simplifying SOQL with Polymorphic RelationshipsHello Trailblazers,<div><br /></div><div>In this post, we're going to learn about <b>TYPEOF</b> clause in SOQL using which we can query data that contains polymorphic relationships. I recently came across this <a href="https://salesforce.stackexchange.com/questions/86086/unable-to-query-federationidentifier-on-case-owner" target="_blank"><span style="color: #2b00fe;">Stackoverflow Question</span></a> which was asked 7 years 3 months ago considering the time I am writing this post where the user has asked about <b>"Unable to query FederationIdentifier on case owner"</b> The question is pretty old but is still valid. You cannot query case owner's FederationIdentifier using a simple SOQL query on case object. Let's see why?</div><div><br /></div><h2 style="text-align: left;">The Problem</h2><div>I have a case record in my org as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvfLAsyIr7oWk3z50k_a2lVGwucDsIFG9WiVcmQ3NhhXm1Qty_N0Qffw2zhvTzSXclU7KlHsMvdVJ0ylseIFLPc_P_wfAoXgCTDZ0mKxKBvxFEQoMoze9rL7_eL3d_cOMxZMjj3sY5Nhg5MoKERR2C-BYb0hgwwaje6qFEYwyxcjkPOIIC8oWqpEoC/s2214/Screenshot%202022-11-26%20at%207.05.08%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1718" data-original-width="2214" height="496" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvfLAsyIr7oWk3z50k_a2lVGwucDsIFG9WiVcmQ3NhhXm1Qty_N0Qffw2zhvTzSXclU7KlHsMvdVJ0ylseIFLPc_P_wfAoXgCTDZ0mKxKBvxFEQoMoze9rL7_eL3d_cOMxZMjj3sY5Nhg5MoKERR2C-BYb0hgwwaje6qFEYwyxcjkPOIIC8oWqpEoC/w640-h496/Screenshot%202022-11-26%20at%207.05.08%20AM.png" width="640" /></a></div><div><br /></div><div>Let's say you want to query the case subject, as well as the case owner's FederationIdentifier field. As you can see above, the owner of case is a person named <b>User User</b> and as we open this user's record, you can see below that the Federation ID field has a value <b>12345</b>.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiYVqnKVDhGPdIh62kk7rkyy8aCOCaFenBC8YSzALfLDtvX0phYmJ8-RaB9hHVjjOzwelN4B2LECs2fOVOa8S3_N9h3Z1PWB57alsgTeGWNe_VOtkB-dTmIN7RWWcKJyxf0QReJaJ6y6C_nTRiIgGmh11FOFpHlOBVPLM30J2dFKQWG6JM_g6g5h2i/s1498/Screenshot%202022-11-02%20at%208.09.12%20PM.png" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="1498" data-original-width="1214" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiYVqnKVDhGPdIh62kk7rkyy8aCOCaFenBC8YSzALfLDtvX0phYmJ8-RaB9hHVjjOzwelN4B2LECs2fOVOa8S3_N9h3Z1PWB57alsgTeGWNe_VOtkB-dTmIN7RWWcKJyxf0QReJaJ6y6C_nTRiIgGmh11FOFpHlOBVPLM30J2dFKQWG6JM_g6g5h2i/w518-h640/Screenshot%202022-11-02%20at%208.09.12%20PM.png" width="518" /></a></div><div></div><div><br /></div><div>Well, if I tell you to write a SOQL query in order to get this federation id from case, maybe the first query that comes to your mind is something like this:</div><pre>SELECT Subject, Owner.FederationIdentifier FROM Case WHERE Id = '5005D000008nxIkQAI'</pre><div>Now, let's run this SOQL query and see the results:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZIgHgYa8NECgSJeG6a7yhlpt0pb83SgCYX-4eOQOuPPyQ6-7WebR6Uu8lTU-AGoyH-IXB4U1IhtUa7eSB45r0si3JV0tpBSJa5Nif2C74qlIYkvC8zdPXcbl4dRDiJ7oF36syDTf1t_pcGbf5sqNvE7elePJL8ocEIqhDDej6SQU4DYxiIUehOKrz/s2168/Screenshot%202022-11-26%20at%207.08.04%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="756" data-original-width="2168" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZIgHgYa8NECgSJeG6a7yhlpt0pb83SgCYX-4eOQOuPPyQ6-7WebR6Uu8lTU-AGoyH-IXB4U1IhtUa7eSB45r0si3JV0tpBSJa5Nif2C74qlIYkvC8zdPXcbl4dRDiJ7oF36syDTf1t_pcGbf5sqNvE7elePJL8ocEIqhDDej6SQU4DYxiIUehOKrz/w640-h224/Screenshot%202022-11-26%20at%207.08.04%20AM.png" width="640" /></a></div>As you can see above, we're getting this error:<pre>SELECT Subject, Owner.FederationIdentifier FROM
^
ERROR at Row:1:Column:17
No such column 'FederationIdentifier' on entity 'Name'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.</pre><div>This error is coming because the OwnerId field on Case is a <b>polymorphic relationship field</b> which can <b>refer to a Group or a User</b>. In case of a User, we'll have the FederationIdentifier field but in case of a Group, we won't have it. Therefore, we can say that, while you're executing this query, salesforce is not sure if the owner is a Group or a User and therefore, the query could not be executed.</div><div><br /></div><div>Ok, got the issue now but how to resolve it? Should I create a formula field on case to get the case owner's federation id? You can do it, but let's also see how we can query it!</div><div><br /></div><h2 style="text-align: left;">The Solution</h2><div>Salesforce has provided an optional <b>TYPEOF</b> clause that can be used in a SOQL query including polymorphic relationships. TYPEOF clause is available in API version 46.0 or LATER. Let's solve our issue first and then we'll see the format of TYPEOF clause in detail. In order to query FederationIdentifier from case owner we can write a query as follows:</div><pre>SELECT Id, TYPEOF Owner WHEN User Then FederationIdentifier END FROM Case WHERE Id = '5005D000008nxIkQAI' AND Owner.Type = 'User'</pre><div>As you can see above, I've included the statement <b>TYPEOF Owner WHEN User Then FederationIdentifier END</b>. It's basically saying that when the Case Owner's type is User, then query FederationIdentifier field. I've also added a Type filter for case owner at the end of query: <b>AND Owner.Type = 'User'</b>. The result for this query is given below:</div><pre>Starting SFDX: Execute SOQL Query...
07:30:06.358 sfdx force:data:soql:query --query SELECT Id, TYPEOF Owner WHEN User Then FederationIdentifier END FROM Case WHERE Id = '5005D000008nxIkQAI' AND Owner.Type = 'User'
Querying Data... done
ID OWNER
────────────────── ──────────────────────────────────────────────────────────────────
5005D000008nxIkQAI {
"attributes": {
"type": "User",
"url": "/services/data/v56.0/sobjects/User/0055D000005BiagQAC"
},
"FederationIdentifier": "12345"
}
Total number of records retrieved: 1.
07:30:10.900 sfdx force:data:soql:query --query SELECT Id, TYPEOF Owner WHEN User Then FederationIdentifier END FROM Case WHERE Id = '5005D000008nxIkQAI' AND Owner.Type = 'User'
ended with exit code 0</pre><div>As you can notice above, we're getting the related Owner record with FederationIdentifier field in response. Let's say you want to get the FederationIdentifier using this query in apex, you can refer to the below code:</div><pre>List<Case> caseList = [SELECT Id, TYPEOF Owner WHEN User Then FederationIdentifier END FROM Case WHERE Id = '5005D000008nxIkQAI' AND Owner.Type = 'User'];
User caseOwner = caseList[0].Owner;
System.debug(caseOwner.FederationIdentifier);</pre><div>You'll get the FederationIdentifier from case owner in the debug as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVueGSuN8OQMvzEip--K_mZGXuJKHkCRHA2B__hzkihAzrFxG3FBnDzhsHe5LM0_XDWNjMax_uEjDiooUN3u5_DgFw2Lf7e3oMbXgem-aft5MS8gCKTMvyv4rxvQHLhkEXU5PvRGArACyrpsDIhXMdt_liXew9DLmUh33qed6gMSNqoq2QANpXgQwv/s2194/Screenshot%202022-11-26%20at%209.42.02%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="666" data-original-width="2194" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVueGSuN8OQMvzEip--K_mZGXuJKHkCRHA2B__hzkihAzrFxG3FBnDzhsHe5LM0_XDWNjMax_uEjDiooUN3u5_DgFw2Lf7e3oMbXgem-aft5MS8gCKTMvyv4rxvQHLhkEXU5PvRGArACyrpsDIhXMdt_liXew9DLmUh33qed6gMSNqoq2QANpXgQwv/w640-h194/Screenshot%202022-11-26%20at%209.42.02%20AM.png" width="640" /></a></div><div>Awesome! Now that we've solved the issue, let's learn more about the TYPEOF clause. The TYPEOF statement in SOQL is of the format:</div><div><br /></div><div><b>TYPEOF <PrimarySObjectField> WHEN <PossibleRelatedObject1APIName> THEN < PossibleRelatedObject1FieldsListToQuery> WHEN <PossibleRelatedObject2APIName> THEN < PossibleRelatedObject2FieldsListToQuery>.... ELSE <LisfOfFieldsToQueryWhenAllAboveWHENConditionsAreFalse> END</b></div><div><br /></div><div>It's similar to switch case in apex. For example, we can understand our TYPEOF clause as: <b>TYPEOF Owner WHEN User Then FederationIdentifier END</b>, here we checked if the <b>TYPEOF</b> Owner (primary sObject field) <b>WHEN</b> (is equal to) User (possible related object) <b>THEN</b> FederationIdentifier (field to query) <b>END</b></div><div><br /></div><div>Notice that the ELSE part is optional and we can have multiple WHEN-THEN combinations to check for multiple objects that are possible considering our polymorphic relationship field.</div><div><br /></div><div>Another example can be <b>WhatId</b> field of <b>Task</b> object. We know that this field can be linked with multiple sObjects. Let's consider Account, Opportunity and Case for now and query different fields from each of these objects. If the task is not linked to any of the Account, Opportunity or Case object, I'll just query the name field of that related object's record.</div><div><br /></div><div>Let's create 4 tasks: First one related to an account, second one related to an opportunity, third one related to a case and fourth one related to a product. Have a look at the tasks I created below:</div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDEUxrVP5dNQDG305ow6w8O5QJgZCHs75ZSfxbdW_1JTRRhH60WSs9bp0wi7VD1p3oSE_hnxgE1TpNbJbnZdzciFddME1ytg325dgL9i0-4WtL8k75M838cWrcCOwLwvpmephvJXn5bNHo9rBt_RK2c6oamytbTATF2Uglpu-eCddtX7e8CsROp2cU/s2880/Screenshot%202022-11-26%20at%2010.24.28%20AM.png" style="margin-left: auto; margin-right: auto;"><img alt="Task linked to account" border="0" data-original-height="1390" data-original-width="2880" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDEUxrVP5dNQDG305ow6w8O5QJgZCHs75ZSfxbdW_1JTRRhH60WSs9bp0wi7VD1p3oSE_hnxgE1TpNbJbnZdzciFddME1ytg325dgL9i0-4WtL8k75M838cWrcCOwLwvpmephvJXn5bNHo9rBt_RK2c6oamytbTATF2Uglpu-eCddtX7e8CsROp2cU/w640-h308/Screenshot%202022-11-26%20at%2010.24.28%20AM.png" title="Task linked to account" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Task linked to account</td></tr></tbody></table><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyeq7tqZqjgL-oHeFLj6bN9ftx5rahTOf_dbqtnDQwrnbEPuuaQcrNamkOGTeeF0qm8X4_VPzUkrmxyEdMe46bz4pvy5V9GEmUfU4mN866XQhfx9xyv-hYuwsExjMxJvUaoteGcmTe_sPsQibQK2HWwqsLI4A7i38o2yBGYSET8gJ3Y-ZglU3e0YWv/s2880/Screenshot%202022-11-26%20at%2010.24.11%20AM.png" style="margin-left: auto; margin-right: auto;"><img alt="Task linked to opportunity" border="0" data-original-height="1452" data-original-width="2880" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyeq7tqZqjgL-oHeFLj6bN9ftx5rahTOf_dbqtnDQwrnbEPuuaQcrNamkOGTeeF0qm8X4_VPzUkrmxyEdMe46bz4pvy5V9GEmUfU4mN866XQhfx9xyv-hYuwsExjMxJvUaoteGcmTe_sPsQibQK2HWwqsLI4A7i38o2yBGYSET8gJ3Y-ZglU3e0YWv/w640-h322/Screenshot%202022-11-26%20at%2010.24.11%20AM.png" title="Task linked to opportunity" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Task linked to opportunity</td></tr></tbody></table><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK4BRtBEQAmjcas044qyIZXNfNa8vi0odHDUopM7KXtMGLpwz4aqIPQtMEM9_ic4Y4pKAycXsQfv9ha4SGD5F6rw1vCblnzIxmgdjDhC7xSqQNn3PDFQl4YBsudfWk93I5-9ojsBR0UmB509vWqvPQ3FxPV98nk9zxq44tbY4xTR8cFhmvv0lCCfks/s2880/Screenshot%202022-11-26%20at%2010.23.52%20AM.png" style="margin-left: auto; margin-right: auto;"><img alt="Task linked to case" border="0" data-original-height="1522" data-original-width="2880" height="338" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK4BRtBEQAmjcas044qyIZXNfNa8vi0odHDUopM7KXtMGLpwz4aqIPQtMEM9_ic4Y4pKAycXsQfv9ha4SGD5F6rw1vCblnzIxmgdjDhC7xSqQNn3PDFQl4YBsudfWk93I5-9ojsBR0UmB509vWqvPQ3FxPV98nk9zxq44tbY4xTR8cFhmvv0lCCfks/w640-h338/Screenshot%202022-11-26%20at%2010.23.52%20AM.png" title="Task linked to case" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Task linked to case</td></tr></tbody></table><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU9M47bgfhpXYOdPEhQGDDmWbt-gLcivqjhOldjcc3Mdq8McyZMdOH6b0pHFsUR-ryegFTWoD0S8dusJ29AX9NuPLF4vPdxfyKvb8Y4P0fXWS5Uuel3kL8vxPy2dCHH2s0pkYhYngpiyPtQCslJZRGjpi9UrLrudDQ2SBxcR8gZATRDuxcl_c6C3o3/s2880/Screenshot%202022-11-26%20at%2010.44.23%20AM.png" style="margin-left: auto; margin-right: auto;"><img alt="Task linked to product" border="0" data-original-height="1452" data-original-width="2880" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU9M47bgfhpXYOdPEhQGDDmWbt-gLcivqjhOldjcc3Mdq8McyZMdOH6b0pHFsUR-ryegFTWoD0S8dusJ29AX9NuPLF4vPdxfyKvb8Y4P0fXWS5Uuel3kL8vxPy2dCHH2s0pkYhYngpiyPtQCslJZRGjpi9UrLrudDQ2SBxcR8gZATRDuxcl_c6C3o3/w640-h322/Screenshot%202022-11-26%20at%2010.44.23%20AM.png" title="Task linked to product" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Task linked to product</td></tr></tbody></table>My apex code with the SOQL query is as follows:<br /><pre>List<Task> taskList = [SELECT Subject,
TYPEOF What
WHEN Account THEN Type, NumberOfEmployees
WHEN Opportunity THEN Amount, CloseDate
WHEN Case THEN Subject, CaseNumber
ELSE Name
END
FROM Task];
for(Task taskRecord: taskList) {
System.debug(taskRecord.Subject + ' -> ' + taskRecord.What);
}</pre><div>As you can see above, I am querying <b>Type</b> and <b>NumberOfEmployees</b> when the related object is <b>Account</b>, <b>Amount</b> and <b>CloseDate</b> when the related object is <b>Opportunity</b>, <b>Subject</b> and <b>CaseNumber</b> when the related object is <b>Case</b>, otherwise, I'm just querying <b>Name</b> of the related record. The result when the above apex code is executed is shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVdY3vXSa1VKWaUsctPfeUeW02cGhudzkaufq8pDAg4hGifzLr1UuIinw2jZ9LxuAcBo1aGQYOMbeEWtvuDrZGZWfo7MGp0uhwNPJ-MIBZ4M-j1085FwQBZuGzFO7fRRPvQk7M5_Wxpi4nFQceWfTd9_xyughybh7JMukBmxMqKsAS6E2FJUc7AAwk/s2314/Screenshot%202022-11-26%20at%2010.59.29%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1496" data-original-width="2314" height="414" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVdY3vXSa1VKWaUsctPfeUeW02cGhudzkaufq8pDAg4hGifzLr1UuIinw2jZ9LxuAcBo1aGQYOMbeEWtvuDrZGZWfo7MGp0uhwNPJ-MIBZ4M-j1085FwQBZuGzFO7fRRPvQk7M5_Wxpi4nFQceWfTd9_xyughybh7JMukBmxMqKsAS6E2FJUc7AAwk/w640-h414/Screenshot%202022-11-26%20at%2010.59.29%20AM.png" width="640" /></a></div>As you can see above, we're getting all the tasks with relevant related fields from each of Account, Opportunity and Case. In case of product, we're getting the Name field. <div><br /></div><div>If you remember, in our initial query on Case (SELECT Id, TYPEOF Owner WHEN User Then FederationIdentifier END FROM Case WHERE Id = '5005D000008nxIkQAI' AND Owner.Type = 'User'), we also added WHERE condition on Type field as: <b>AND Owner.Type = 'User'</b>. This is another good way to filter records while dealing with the specific type of related objects we would like to consider. You can even skip the ELSE part if you've already added WHEN-THEN for each related object and the corresponding conditions in WHERE clause.<div><br /></div><div>So that's how you can <b>Simplify SOQL with Polymorphic Relationships</b>. You can have a look at this <a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_and_polymorph_keys.htm#sforce_api_calls_soql_relationships_and_polymorph_keys" target="_blank"><span style="color: #2b00fe;">detailed article</span></a> from salesforce official documentation to learn more about TYPEOF clause. Before coming to the end of this blog, let's talk about some of the important considerations of TYPEOF clause as well.</div><div><br /></div><div><h2 style="text-align: left;">Considerations for TYPEOF</h2><div>I am highlighting some of the considerations of TYPEOF clause here, you can checkout the full list in <a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_and_polymorph_keys.htm#sforce_api_calls_soql_relationships_and_polymorph_keys" target="_blank"><span style="color: #2b00fe;">official documentation</span></a>.</div><div></div><div><ul style="text-align: left;"><li>TYPEOF cannot be used with with queries that don't return objects such as COUNT() and aggregate queries</li><li>TYPEOF cannot be used in SOQL used in Bulk API</li><li> TYPEOF cannot be used in semi-join query. Semi Join query is a query used to filter the original query - For example: SELECT Name FROM Contact WHERE AccountId IN (<b>SELECT Id FROM Account</b>), here the text in bold is semi-join query</li><li>TYPEOF cannot be used with a relationship field whose <b>relationshipName</b> or <b>namePointing</b> attribute is false.</li></ul><div>You can checkout the <b>namePointing</b> and <b>relationshipName</b> attribute of a field very easily. For example, consider the below code:</div></div><pre>System.debug(Case.OwnerId.getDescribe());</pre><div>Here, we're getting the DescribeFieldResult for <b>OwnerId</b> field of <b>Case</b> object. The output for the same is provided below:</div><pre>Schema.DescribeFieldResult[
getByteLength=18;
getCalculatedFormula=null;
getCompoundFieldName=null;
getController=null;
getDataTranslationEnabled=null;
getDefaultValue=null;
getDefaultValueFormula=null;
getDigits=0;
getFilteredLookupInfo=null;
getInlineHelpText=null;
getLabel=Owner ID;
getLength=18;
getLocalName=OwnerId;
getMask=null;
getMaskType=null;
getName=OwnerId;
getPrecision=0;
getReferenceTargetField=null;
getRelationshipName=Owner; // <---- Relationship Name
getRelationshipOrder=null;
getScale=0;
getSoapType=ID;
getSobjectField=OwnerId;
getType=REFERENCE;
isAccessible=true;
isAggregatable=true;
isAiPredictionField=false;
isAutoNumber=false;
isCalculated=false;
isCascadeDelete=false;
isCaseSensitive=false;
isCreateable=true;
isCustom=false;
isDefaultedOnCreate=true;
isDependentPicklist=false;
isDeprecatedAndHidden=false;
isDisplayLocationInDecimal=false;
isEncrypted=false;
isExternalId=false;
isFilterable=true;
isFormulaTreatNullNumberAsZero=false;
isGroupable=true;
isHighScaleNumber=false;
isHtmlFormatted=false;
isIdLookup=false;
isNameField=false;
isNamePointing=true; // <---- Name Pointing
isNillable=false;
isPermissionable=false;
isQueryByDistance=false;
isRestrictedDelete=false;
isSearchPrefilterable=false;
isSortable=true;
isUnique=false;
isUpdateable=true;
isWriteRequiresMasterRead=false;
]</pre><div>If you notice, the <b>relationshipName</b> for this field is <b>Owner</b> and <b>namePointing</b> attribute is also <b>true</b>. A smaller documentation on TYPEOF clause is also available <a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_typeof.htm" target="_blank"><span style="color: #2b00fe;">here</span></a>.</div><div><br /></div><div>That's all for this tutorial. I hope you liked it, let me know your feedback in the comments down below. You can connect with my on <a href="https://bit.ly/downloadconnectionsapp" target="_blank"><span style="color: #2b00fe;">Connections</span></a> app by scanning the QR code given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguTjIp8eWnSbConcRmKqCoNQwh0sVicj1hoBMd-WbUu7MeKb4N-cCoT2XhLDt1pwJDDKrgMDV3O2mgEJ7KpZNqs8lYlJ3VqshQcwEc1XyL5oTGVaePgwHyB8NH-XmdQ62qcIYNuGohqTXMa0Y-nihQ3aPhkYHHFcPz_v__JysweglemNKHVBgMR6o3/s1208/Screenshot%202022-11-26%20at%2012.00.35%20PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1008" data-original-width="1208" height="267" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguTjIp8eWnSbConcRmKqCoNQwh0sVicj1hoBMd-WbUu7MeKb4N-cCoT2XhLDt1pwJDDKrgMDV3O2mgEJ7KpZNqs8lYlJ3VqshQcwEc1XyL5oTGVaePgwHyB8NH-XmdQ62qcIYNuGohqTXMa0Y-nihQ3aPhkYHHFcPz_v__JysweglemNKHVBgMR6o3/s320/Screenshot%202022-11-26%20at%2012.00.35%20PM.png" width="320" /></a></div><div>Or search in the code scanner screen using my username: <b>rahulmalhotra</b></div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-21093638712488525522022-11-19T06:53:00.001-08:002022-11-19T06:53:10.337-08:00Create modals using the new LightningModal component (Winter '23 Release)Hello Trailblazers,<div><br /></div><div>In this post we're going to learn about the new <b>LightningModal</b> component that can be extended by any lwc which you would like to use as a modal. Salesforce has provided 3 helper components to create a modal:</div><div><br /></div><div><ol style="text-align: left;"><li>lightning-modal-header<br /></li><li>lightning-modal-body<br /></li><li>lightning-modal-footer</li></ol><div><br /></div><div>Let's create a <b>testModal</b> component and try to use these 3 tags to see what we get as a result.</div></div><div><br /></div><h2 style="text-align: left; text-transform: none;">Creating a simple testModal LWC</h2><h3 style="text-align: left;">testModal.js-meta.xml</h3><div>I am specifying the meta file first of all so that we can focus on our html and js files throughout this tutorial. Our meta file is pretty simple as shown below:</div><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre><div><br /></div><div>As you can see above, I've specified <b>lightning__HomePage</b> as a target so that we can embed this component in our homepage. This is <b>NOT REALLY REQUIRED</b> as we're going to use our component as a modal, but I'm just keeping this for now, so that we can see what the helper components render for us. I've also marked <b>isExposed</b> as true to make this component available in our app builder.</div><h3 style="text-align: left;">testModal.html</h3><div>The simplest HTML content for our modal is shown below:</div><pre><template>
<lightning-modal-header>Test Modal Header</lightning-modal-header>
<lightning-modal-body>Test Modal Body</lightning-modal-body>
<lightning-modal-footer>Test Modal Footer</lightning-modal-footer>
</template></pre><div><br /></div><div>We've only called the header body and footer tags with some content in them. I embedded our component on the homepage. Let's see the output below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYH8zU5WOhzYZm-JBUL7GrpUzw61PQ87tomuxPOFr7is21rRk2-gfEITF4uWtiNQW2t2PPlmL0V6Uokiy7Mdiga4NbfLKdTSM8jC5ccc61S8wZoQgpV4QUykP-axweeUGrtk-CHSBrb-ZoA7xc-2t3RJMX6laNuhT800YtzXQfwEVPKhEa_doMp5Qf/s1458/Screenshot%202022-11-19%20at%2011.27.37%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="556" data-original-width="1458" height="244" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYH8zU5WOhzYZm-JBUL7GrpUzw61PQ87tomuxPOFr7is21rRk2-gfEITF4uWtiNQW2t2PPlmL0V6Uokiy7Mdiga4NbfLKdTSM8jC5ccc61S8wZoQgpV4QUykP-axweeUGrtk-CHSBrb-ZoA7xc-2t3RJMX6laNuhT800YtzXQfwEVPKhEa_doMp5Qf/w640-h244/Screenshot%202022-11-19%20at%2011.27.37%20AM.png" width="640" /></a></div><br /><div>As you can see above, we're having 3 different sections: Header, Body and Footer coming from our lightning-modal-header, lighnting-modal-body and lightning-modal-footer tags.</div><div><br /></div><div>It's better and easier to use <b>label</b> attribute of lightning-modal-header to specify the heading for our modal header. You can just update the <b>lightning-modal-header</b> tag as shown below:</div><pre><lightning-modal-header label="Test Modal Label">Test Modal Header</lightning-modal-header></pre><div>Let's see the output of this change as well:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMPrT996W_biraQ-1uKVxHryHxE-20TpwLgyivTBnFVnn1lf9g2K8yOZMg8DYDDFLMGIXwyCCXwCXH6-gIhG_F5kBjO-1F8wrggzv_H3rn_E9sf2BYxbWXYTtjXPzaj1KS1TkIvt8gqv2etOSc97VAK2zJ5HRFL5BZjqx2XBfMYbhCSiAx-7xdZoh8/s962/Screenshot%202022-11-19%20at%201.19.46%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="372" data-original-width="962" height="155" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMPrT996W_biraQ-1uKVxHryHxE-20TpwLgyivTBnFVnn1lf9g2K8yOZMg8DYDDFLMGIXwyCCXwCXH6-gIhG_F5kBjO-1F8wrggzv_H3rn_E9sf2BYxbWXYTtjXPzaj1KS1TkIvt8gqv2etOSc97VAK2zJ5HRFL5BZjqx2XBfMYbhCSiAx-7xdZoh8/w400-h155/Screenshot%202022-11-19%20at%201.19.46%20PM.png" width="400" /></a></div><div>You might not see any difference here but it'll be more clear as we'll start using this component as a modal.</div><h3 style="text-align: left; text-transform: none;">Okay that's fine but how do I actually use this as a modal?</h3><div>In order to open it as a modal, we'll create another lwc named: <b>useModal</b> but first of all let's update the js file of this testModal as well:</div><h3 style="text-align: left;">testModal.js</h3><pre>import LightningModal from 'lightning/modal';
export default class TestModal extends LightningModal {}</pre><div>Notice the two changes I did above which makes it different from other LWCs:<br /><br /></div><div><ol style="text-align: left;"><li>Instead of the statement, <b>import { LightningElement } from 'lwc';</b> it's importing <b>LightningModal</b> from <b>lightning/modal</b>.</li><li>Instead of extending <b>LightningElement</b>, our component is extending <b>LightningModal</b> using <b>extends LightningModal</b>.</li></ol><br />That's all for our testModal for now, let's create our useModal component now:</div><h3 style="text-align: left;"><b>useModal.js-meta.xml</b></h3><div>Starting with the meta file, it's the exact same as we had above because we'll be embedding this component as well in our homepage:</div><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre><div><b>Note</b>: You can remove the lightning__HomePage target from your testModal now and remove it from the hompage, as we'll be using it as a modal now</div><div><br /></div><div>Now, let's move on to the HTML part of useModal LWC:</div><h3 style="text-align: left;">useModal.html</h3><pre><template>
<lightning-card title="Use Modal">
<p class="slds-var-p-horizontal_small">
<lightning-button label="Open Modal" onclick={openModal}></lightning-button>
</p>
</lightning-card>
</template></pre><div>As you can see above, the code is pretty simple. We have a lightning-card (for better UI) and inside it, we have a paragraph which contains a lightning-button with label <b>Open Modal</b>. This button will call our js function <b>openModal</b> as it's clicked by the user. It's time to move on to our js file now!</div><h3 style="text-align: left;">useModal.js</h3><div>For now, we'll just try to open our <b>testModal</b>:</div><pre>import { LightningElement } from 'lwc';
import TestModal from 'c/testModal';
export default class UseModal extends LightningElement {
openModal() {
TestModal.open();
}
}</pre><div>As you can see above, we've imported our <b>TestModal</b> using the import statement and called <b>open()</b> on it in order to open our modal component. Let's see how it works!</div><div><br /></div><div>This is how our <b>useModal</b> component looks like on the homepage:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhNLGqNof2fB-39q-uyfA0ra56MiFRbL1oJKLHl1bJi6OhpIvRKFdvuCMtCCaNAo_GPqWg1L8-Mjs1mJ-3zHzeARsaO041fpCfyGECX_SkCRQU08PM4JNnmDX0fBecXNFzZ-zreA1SjVB8LYUNEUcTzBJU5npUh4qOCfptHnouzuMn4FRWbo_H2N-o/s1326/Screenshot%202022-11-19%20at%201.22.04%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1326" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhNLGqNof2fB-39q-uyfA0ra56MiFRbL1oJKLHl1bJi6OhpIvRKFdvuCMtCCaNAo_GPqWg1L8-Mjs1mJ-3zHzeARsaO041fpCfyGECX_SkCRQU08PM4JNnmDX0fBecXNFzZ-zreA1SjVB8LYUNEUcTzBJU5npUh4qOCfptHnouzuMn4FRWbo_H2N-o/w640-h370/Screenshot%202022-11-19%20at%201.22.04%20PM.png" width="640" /></a></div><div>I just embedded it before our testModal component. As we click on <b>Open Modal</b> button, we get the modal as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFrfxqzhh9f42vCkeOcyPe4W66NMDQiwHOW4c29hyv3euwIWR_g5hnDoJvqNQenIkpX7O02YBkI3wO5f1R9Y-_UaUvRbxCA9HzApoYiQaM5CqfWhx7gp5XvayqH66DkjomuguCs-NTkMI7eI6s21NS5Clv4_lF_u4Y7wxtJewtkH3jlPMKPmPbcmB7/s2880/Screenshot%202022-11-19%20at%201.23.48%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1642" data-original-width="2880" height="364" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFrfxqzhh9f42vCkeOcyPe4W66NMDQiwHOW4c29hyv3euwIWR_g5hnDoJvqNQenIkpX7O02YBkI3wO5f1R9Y-_UaUvRbxCA9HzApoYiQaM5CqfWhx7gp5XvayqH66DkjomuguCs-NTkMI7eI6s21NS5Clv4_lF_u4Y7wxtJewtkH3jlPMKPmPbcmB7/w640-h364/Screenshot%202022-11-19%20at%201.23.48%20PM.png" width="640" /></a></div><div>I hope that the usage of <b>label</b> attribute in <b>lightning-modal-header</b> component is clear now. You should use label attribute to specify a label for your modal. If you want to have any custom HTML like: a button or something else, that can come between our <b>lightning-modal-header</b> tags.</div><div><br /></div><div>Isn't it amazing? just a few lines of code and you have your modal ready. The close button that you see on the top right works perfectly and your modal will be closed automatically as you click on that button.</div><div><br /></div><div><b>Note</b>: You can also close the modal by pressing the ESC key.</div><h3 style="text-align: left;">Resizing our modal</h3><div>There are some properties that you can pass in the open function. One of the property is <b>size</b> which supports <b>small</b>, <b>medium</b> and <b>large</b> values. By default, the size is <b>medium</b>. You can pass any sizes out of small, medium or large. The output of all 3 are shown below.</div><div><br /></div><div>To have a small sized modal, you can just do:</div><pre> TestModal.open({
size: 'small'
});</pre><div>And you'll have the below output:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnmK7S109m334Zd6ivV-u8Uc3FXxXgcbDhx58ATJbt7qvh8ABTmWYrhcLb79S0yXMnKW8jLhHnwARqVj40vrSOsE-Sqd8hEsT6L0AJNCoapHqKiKGgBIumSGH-2c3oDCOgSmqJz15u-TnkopHwyUyG57c54UIhxipLbNDGu7WFyD4SAh5OoIriWQAs/s2880/Screenshot%202022-11-19%20at%201.28.40%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1642" data-original-width="2880" height="364" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnmK7S109m334Zd6ivV-u8Uc3FXxXgcbDhx58ATJbt7qvh8ABTmWYrhcLb79S0yXMnKW8jLhHnwARqVj40vrSOsE-Sqd8hEsT6L0AJNCoapHqKiKGgBIumSGH-2c3oDCOgSmqJz15u-TnkopHwyUyG57c54UIhxipLbNDGu7WFyD4SAh5OoIriWQAs/w640-h364/Screenshot%202022-11-19%20at%201.28.40%20PM.png" width="640" /></a></div><div><div>For a medim sized modal, you can just do nothing (as the default size is medium) or pass in the medium value as shown below:</div><pre> TestModal.open({
size: 'medium'
});</pre><div>And you'll have the below output:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEIox1bxe8h1m0DEC6rESXgnY9EDefvx5NWsDlyWeukiXswaQ-8lFWpZEQuR3_IKPttkV5MtSBznbgUpLfymvTB28R2qxqDJ34GTZ4cJyaU9AbZuiphqFnsFjK8xd1puD-sMC3OrixosbW7IgdGojzih1ByliwVAr5TEhkJpI_RycE87BMruGTXPDP/s2880/Screenshot%202022-11-19%20at%201.23.48%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1642" data-original-width="2880" height="364" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEIox1bxe8h1m0DEC6rESXgnY9EDefvx5NWsDlyWeukiXswaQ-8lFWpZEQuR3_IKPttkV5MtSBznbgUpLfymvTB28R2qxqDJ34GTZ4cJyaU9AbZuiphqFnsFjK8xd1puD-sMC3OrixosbW7IgdGojzih1ByliwVAr5TEhkJpI_RycE87BMruGTXPDP/w640-h364/Screenshot%202022-11-19%20at%201.23.48%20PM.png" width="640" /></a></div><div>Similarly, for a large modal, you can just do:</div><div><pre> TestModal.open({
size: 'large'
});</pre><div>And you'll have the below output:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF4h-hwZvX7HU_0INXhioTGW7-QxYd6w1QcELOmlWt1Ozp7zTvwoTB57qFwTxICeDQ85ud2pqlOk9kj54AJp8am6ZghmjL_9buhoVNpK6QaCFIug9tj--Q3u3SBXTpMk2JvYnR2A-TeAWM1LeuaD_zDCWnX90RKiPe2XPfmwSoqBVtFmF9i3Pd0Kug/s2880/Screenshot%202022-11-19%20at%201.31.03%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1644" data-original-width="2880" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF4h-hwZvX7HU_0INXhioTGW7-QxYd6w1QcELOmlWt1Ozp7zTvwoTB57qFwTxICeDQ85ud2pqlOk9kj54AJp8am6ZghmjL_9buhoVNpK6QaCFIug9tj--Q3u3SBXTpMk2JvYnR2A-TeAWM1LeuaD_zDCWnX90RKiPe2XPfmwSoqBVtFmF9i3Pd0Kug/w640-h366/Screenshot%202022-11-19%20at%201.31.03%20PM.png" width="640" /></a></div><h3 style="text-align: left;">Defining a Custom Close Button for our Lightning Modal</h3></div></div><div>A very common requirement is to have two buttons: <b>Cancel</b> and <b>Save</b> in our modal footer. If someone clicks on Cancel, we'll just close the modal and if someone clicks on Save, we'll save the information and then close our modal. Let's see how we can implement that. It's time to update our <b>testModal.html</b></div><pre><template>
<lightning-modal-header>Test Modal Header</lightning-modal-header>
<lightning-modal-body>Test Modal Body</lightning-modal-body>
<lightning-modal-footer>
<lightning-button label="Cancel" onclick={closeModal} class="slds-var-p-right_x-small"></lightning-button>
<lightning-button label="Save" variant="brand" onclick={save}></lightning-button>
</lightning-modal-footer>
</template></pre><div>As you can see above, I've removed the <b>Test Modal Footer</b> text which was present in between lightning-modal-footer tags and I added two lightning buttons instead: one for cancel and another for save. The <b>Cancel</b> button is calling <b>closeModal()</b> from our js and have an extra small right padding so that the two buttons don't stick to each other. The <b>Save</b> button is having a <b>variant</b> as <b>brand</b> and is calling <b>save()</b> from our js file. The updated <b>testModal.js</b> file is given below:</div><pre>import LightningModal from 'lightning/modal';
export default class TestModal extends LightningModal {
closeModal() {
this.close();
}
save() {
console.log('We will save the data and then close modal');
}
}</pre><div><br /></div><div>You might have noticed above that I added two methods here:</div><div><ol style="text-align: left;"><li><b>closeModal()</b> which is calling <b>close()</b> here as <b>this.close()</b>. This close() is defined in the LightningModal component which we're extending and it'll close the modal.<br /></li><li><b>save()</b> which is doing nothing as of now but adding a statement to the console that: <b>We'll save the data and then close modal</b>.</li></ol></div><div><br /></div><div>The updated modal is shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh21qMaS1FcKDdjoVKNPBpLZMBlbzZurIhYdIyvrabKucIWVw_lSK653xTmuuJ9a7rlLKp0efX7Nm0hgclztTrra8U1GQaf58fTSg8w5sEzD9uGVvDXY-DEUTohsLvO70JfydItpHz4lVqd0k1kE6APOo8Ys27nX7DsdyBLj9prOGl8995pif44NZkx/s2880/Screenshot%202022-11-19%20at%201.34.04%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1644" data-original-width="2880" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh21qMaS1FcKDdjoVKNPBpLZMBlbzZurIhYdIyvrabKucIWVw_lSK653xTmuuJ9a7rlLKp0efX7Nm0hgclztTrra8U1GQaf58fTSg8w5sEzD9uGVvDXY-DEUTohsLvO70JfydItpHz4lVqd0k1kE6APOo8Ys27nX7DsdyBLj9prOGl8995pif44NZkx/w640-h366/Screenshot%202022-11-19%20at%201.34.04%20PM.png" width="640" /></a></div><div>As you can see, we have two buttons now: <b>Cancel</b> and <b>Save</b>. As you click on <b>Cancel</b> button, the modal will be closed. If you click on <b>Save</b> button, there will be a message in console but the modal will not close.</div><div><br /></div><div>Let's say the user clicks on save and then while the information is being saved, the user clicks on <b>Close</b> button at the top right and the modal is closed, how do you ensure that the information was saved successfully? In this scenario, preventing the user from accidentally closing the modal is important, let's see how we can do that!</div><h3 style="text-align: left;">Prevent the user from closing Lightning Modal using disableClose attribute</h3><div>For now, we'll consider a scenario that our save operation takes 5 seconds. So, we'll disable the <b>Close</b> operation for 5 seconds when the save button is clicked. We're not dealing with apex in this tutorial, so we'll just use <b>setTimeout()</b> to simulate our server call. Our <b>testModal.js</b> is updated as shown below:</div><pre>import LightningModal from 'lightning/modal';
export default class TestModal extends LightningModal {
closeModal() {
this.close();
}
save() {
console.log('We will save the data and then close modal');
this.disableClose = true;
const that = this;
setTimeout(() => {
console.log('Information saved! You can now close the modal');
that.disableClose = false;
}, 5000);
}
}</pre><div>Inside the <b>save()</b>, we're setting <b>disableClose</b> to <b>true</b>. Then we're calling <b>setTimeout()</b> (you can consider it similar to calling any apex method and waiting for the response). In setTimeout(), we can pass a function and specify the time (in milliseconds) after which that function will be called. Here, we've specified the time as <b>5000</b> <b>milliseconds</b> i.e. <b>5 seconds</b> and after 5 seconds the function passed will be called. That function will print the text <b>Information saved! You can now close the modal</b> in the console and set <b>disableClose</b> to <b>false</b> again. This can be considered - as our save operation is successful and we want to allow the user to close the modal now.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzIMTTHKqM-Mzhb9Fb3AE_QUwMglUtJFUMI82Xr6CSRe4yzOm446rmK5fyYW2aTOWlNQkQQzTGb_cwTl6jAeIzuonSVnJl5ojd4OjSKImRejXsOJEiaDvGzZAnpaClm9rNtdCjlPswGFVtwSyOgKMFHtB2eXeATBuIWYwYFxeCjMs0mnuKreClyHa6/s2880/Screenshot%202022-11-19%20at%204.41.38%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1144" data-original-width="2880" height="254" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzIMTTHKqM-Mzhb9Fb3AE_QUwMglUtJFUMI82Xr6CSRe4yzOm446rmK5fyYW2aTOWlNQkQQzTGb_cwTl6jAeIzuonSVnJl5ojd4OjSKImRejXsOJEiaDvGzZAnpaClm9rNtdCjlPswGFVtwSyOgKMFHtB2eXeATBuIWYwYFxeCjMs0mnuKreClyHa6/w640-h254/Screenshot%202022-11-19%20at%204.41.38%20PM.png" width="640" /></a></div>Notice the above image, this is how our modal looks like. Have a look at the cross in the red rectangle, it's enabled for now. As I click Save button the modal will look like as shown below:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbsv_zW0s95hqF_EjbtiL43WLDsqvSsmLhQVQRSf2NdE0tqaDDZskD0Zgwf3b83NTAPYPU9vOBlEDUNZZEyLTEAt51J_xTUPZVDNA20m95iUTO8EHtLrHP0WVf60WEENpEWlRnGNV4Ri24FQL5uOmFefkNPziKWP7SzDndKilBSP0fpaxvWcoBX5x3/s2880/Screenshot%202022-11-19%20at%204.44.23%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1042" data-original-width="2880" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbsv_zW0s95hqF_EjbtiL43WLDsqvSsmLhQVQRSf2NdE0tqaDDZskD0Zgwf3b83NTAPYPU9vOBlEDUNZZEyLTEAt51J_xTUPZVDNA20m95iUTO8EHtLrHP0WVf60WEENpEWlRnGNV4Ri24FQL5uOmFefkNPziKWP7SzDndKilBSP0fpaxvWcoBX5x3/w640-h232/Screenshot%202022-11-19%20at%204.44.23%20PM.png" width="640" /></a></div>As you can see above, the cross icon is disabled, this means we cannot close our modal and it'll automatically be enabled after 5 seconds. Even if you click on <b>Cancel</b> button the modal will not close because the call <b>this.close()</b> will not work. It's even better if we can disable the <b>Save</b> and <b>Cancel</b> buttons as well, until the save operation is performed, so that the user doesn't click these buttons again and again. Let's do that quickly!<div><br /></div><div>For this, I am going to add <b>disabled={disableClose}</b> to both Save and Cancel buttons of my modal so that these buttons are disabled when <b>disableClose</b> is true. Below is the updated <b>testModal.html</b>:</div><pre><template>
<lightning-modal-header label="Test Modal Label">Test Modal Header</lightning-modal-header>
<lightning-modal-body>Test Modal Body</lightning-modal-body>
<lightning-modal-footer>
<lightning-button label="Cancel" onclick={closeModal} class="slds-var-p-right_x-small" disabled={disableClose}></lightning-button>
<lightning-button label="Save" variant="brand" onclick={save} disabled={disableClose}></lightning-button>
</lightning-modal-footer>
</template></pre><div>Notice the <b>disabled</b> attribute applied to both the buttons above. The updated output as I click on <b>Save</b> button of my modal is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW7VwPRfyrB6Uz0R-kybhbNKf0alq9YZUqiytidB76eVBSo36mhlsY7dBcJ5MoZ-fTnfkkMOC4pDxsORtCg2nZaT3LANNJY9NPO_VBBT96db1w_-w5SUCRG-_moghhKLgcRYmWHPjqmoncrR5nsA5-SHNdp3-muG4rIvahcjKE7xY45ntFxBuHXRv8/s2880/Screenshot%202022-11-19%20at%204.51.41%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1048" data-original-width="2880" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW7VwPRfyrB6Uz0R-kybhbNKf0alq9YZUqiytidB76eVBSo36mhlsY7dBcJ5MoZ-fTnfkkMOC4pDxsORtCg2nZaT3LANNJY9NPO_VBBT96db1w_-w5SUCRG-_moghhKLgcRYmWHPjqmoncrR5nsA5-SHNdp3-muG4rIvahcjKE7xY45ntFxBuHXRv8/w640-h232/Screenshot%202022-11-19%20at%204.51.41%20PM.png" width="640" /></a></div>As you can see above, all my buttons are disabled now when I clicked <b>Save</b>, they'll be enabled back together after 5 seconds (OR you can do it after your apex call is successful in a real implementation). You can also call the <b>close()</b> again once your apex call is successful so that the modal get closed automatically. In our case, it can be after 5 seconds when we set <b>disableClose</b> back to <b>false</b> as shown below:<pre>import LightningModal from 'lightning/modal';
export default class TestModal extends LightningModal {
closeModal() {
this.close();
}
save() {
console.log('We will save the data and then close modal');
this.disableClose = true;
const that = this;
setTimeout(() => {
console.log('Information saved! Closing the modal...');
that.disableClose = false;
that.close();
}, 5000);
}
}</pre><div>Notice that we called <b>close()</b> after we set <b>disableClose</b> to false in our <b>save()</b>. Now, the modal will close automatically after 5 seconds as we click on the save button.<div><div><br /></div><div>So that's how you can create modals using the new <b>LightningModal</b> component. I hope you have a good idea of how it works and you can go ahead and create your own modals now. Some of the information that we didn't cover here are:</div><div><ul style="text-align: left;"><li>Custom styling of modal's header, footer and body</li><li>Passing information to modal while opening the modal (Just create an @api attribute in your testModal and provide value to it in the TestModal.open() like we did for <b>size</b> attribute)</li><li>Passing information back to component that called the modal when the modal is closed (You can pass the value inside close() as a parameter and have a then() linked to TestModal.open() which will receive the return value as: TestModal.open({...params}).then((valuePassedToCloseFunction) => {})</li><li>Firing an event from modal and capturing it in parent.</li></ul></div><div>You can refer to the <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-modal/documentation" target="_blank"><span style="color: #2b00fe;">official documentation</span></a> or comment below and let me know if you would like to learn about these in detail and I'll create another post. You can also find my contact details on <a href="https://bit.ly/downloadconnectionsapp" target="_blank"><span style="color: #2b00fe;">Connections</span></a> (username: <b>rahulmalhotra</b>)</div><div><br /></div><div>So that's all for this tutorial. I hoped you liked it, let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-11970529813551330032022-10-25T22:25:00.000-07:002022-10-25T22:25:16.739-07:00How to pass data from lwc to screen flow in salesforce?<p>Hello Trailblazers,</p><p><br /></p><p>In the previous post, we learned: <a href="https://www.sfdcstop.com/2022/10/how-to-pass-data-from-screen-flow-to.html" target="_blank"><span style="color: #2b00fe;">How to pass data from screen flow to lwc in salesforce?</span></a> In this post we're going to learn, how we can pass data from lightning web component (LWC) to the parent screen flow in salesforce. Let's begin by creating our LWC first.</p><p><br /></p><h2 style="text-align: left;">LWC: createLead</h2><div>This LWC will be used to create an instance of lead with some basic information (first name, last name, company) and pass it to the flow, it won't insert the lead record in salesforce. Once the lead record is received by the flow, we'll populate some other fields with default values like: Lead Source and Rating and then create the lead record from the flow itself. Let's Begin!</div><h3 style="text-align: left;">createLead.html</h3><div>We have a simple lightning-card element and 2 lightning-input-field elements (to store first and last name). We also have 1 lightning-combobox element (to store company) as shown below:</div><pre><template>
<lightning-card title={title}>
<lightning-layout multiple-rows="true">
<lightning-layout-item size="12" padding="around-small">
<lightning-input type="text" name="FirstName" label="First Name" required></lightning-input>
</lightning-layout-item>
<lightning-layout-item size="12" padding="around-small">
<lightning-input type="text" name="LastName" label="Last Name" required></lightning-input>
</lightning-layout-item>
<lightning-layout-item size="12" padding="around-small">
<lightning-combobox label="Company" name="Company" placeholder="Select Company" options={companies} required></lightning-combobox>
</lightning-layout-item>
</lightning-layout>
</lightning-card>
</template></pre><div>Notice that all the fields in our form, are marked as requied. We'll pass the title of lightning-card card from our screen flow which will be stored in <b>title</b> property in js and is referenced here in html by the title attribute as <b>title={title}</b>. Let's see the JavaScript code now:</div><h3 style="text-align: left;">createLead.js</h3><div>Have a look at the code snippet below. The default value for our <b>title</b> property is <b>Create Lead</b>. We also have another public attribute named <b>leadRecord</b>, this attribute will be passed to our screen flow with all the lead details. We have a <b>companies</b> array in our code which is used by lightning-combobox, so that the user can select a company for the lead. It's a text field in salesforce but let's consider hypothetically that this companies array is populated by doing a callout to an external system from LWC. We don't want our users to write any company name for our lead record, therefore, the flow to create a lead is as follows:</div><div><br /></div><div><b>Screen flow launched -> LWC component rendered inside screen flow -> Get valid company names by performing a callout to an external system from LWC -> User enter the first name, last name, select a company and click next -> Lead record passed from LWC to screen flow -> Screen flow add some default values for other fields -> Screen flow creates a new lead record in salesforce</b></div><pre>import { api, LightningElement } from 'lwc';
export default class CreateLead extends LightningElement {
@api title = 'Create Lead';
@api leadRecord = {};
companies = [
{
"label": "Apple",
"value": "Apple"
},
{
"label": "Saudi Aramco",
"value": "Saudi Aramco"
},
{
"label": "Microsoft",
"value": "Microsoft"
},
{
"label": "Alphabet (Google)",
"value": "Alphabet (Google)"
},
{
"label": "Amazon",
"value": "Amazon"
},
{
"label": "Tesla",
"value": "Tesla"
},
{
"label": "Berkshire Hathaway",
"value": "Berkshire Hathaway"
},
{
"label": "UnitedHealth",
"value": "UnitedHealth"
},
{
"label": "Johnson & Johnson",
"value": "Johnson & Johnson"
},
{
"label": "Exxon Mobil",
"value": "Exxon Mobil"
},
{
"label": "Visa",
"value": "Visa"
},
{
"label": "Walmart",
"value": "Walmart"
},
{
"label": "JPMorgan Chase",
"value": "JPMorgan Chase"
},
{
"label": "Meta Platforms (Facebook)",
"value": "Meta Platforms (Facebook)"
},
{
"label": "Chevron",
"value": "Chevron"
},
{
"label": "TSMC",
"value": "TSMC"
},
{
"label": "Eli Lilly",
"value": "Eli Lilly"
},
{
"label": "LVMH",
"value": "LVMH"
},
{
"label": "NVIDIA",
"value": "NVIDIA"
},
{
"label": "Procter & Gamble",
"value": "Procter & Gamble"
},
{
"label": "Mastercard",
"value": "Mastercard"
},
{
"label": "Nestlé",
"value": "Nestlé"
},
{
"label": "Tencent",
"value": "Tencent"
},
{
"label": "Home Depot",
"value": "Home Depot"
},
{
"label": "Bank of America",
"value": "Bank of America"
},
{
"label": "Samsung",
"value": "Samsung"
},
{
"label": "Roche",
"value": "Roche"
},
{
"label": "Kweichow Moutai",
"value": "Kweichow Moutai"
},
{
"label": "AbbVie",
"value": "AbbVie"
},
{
"label": "Pfizer",
"value": "Pfizer"
},
{
"label": "Merck",
"value": "Merck"
},
{
"label": "Coca-Cola",
"value": "Coca-Cola"
},
{
"label": "Pepsico",
"value": "Pepsico"
},
{
"label": "Novo Nordisk",
"value": "Novo Nordisk"
},
{
"label": "Costco",
"value": "Costco"
},
{
"label": "Reliance Industries",
"value": "Reliance Industries"
},
{
"label": "ICBC",
"value": "ICBC"
},
{
"label": "Oracle",
"value": "Oracle"
},
{
"label": "Thermo Fisher Scientific",
"value": "Thermo Fisher Scientific"
},
{
"label": "Alibaba",
"value": "Alibaba"
},
{
"label": "Shell",
"value": "Shell"
},
{
"label": "McDonald",
"value": "McDonald"
},
{
"label": "Walt Disney",
"value": "Walt Disney"
},
{
"label": "ASML",
"value": "ASML"
},
{
"label": "Toyota",
"value": "Toyota"
},
{
"label": "Broadcom",
"value": "Broadcom"
},
{
"label": "Danaher",
"value": "Danaher"
},
{
"label": "Cisco",
"value": "Cisco"
},
{
"label": "T-Mobile US",
"value": "T-Mobile US"
},
{
"label": "Astrazeneca",
"value": "Astrazeneca"
},
{
"label": "Wells Fargo",
"value": "Wells Fargo"
},
{
"label": "Accenture",
"value": "Accenture"
},
{
"label": "Novartis",
"value": "Novartis"
},
{
"label": "Abbott Laboratories",
"value": "Abbott Laboratories"
},
{
"label": "L'Oréal",
"value": "L'Oréal"
},
{
"label": "Salesforce",
"value": "Salesforce"
},
{
"label": "ConocoPhillips",
"value": "ConocoPhillips"
},
{
"label": "Bristol-Myers Squibb",
"value": "Bristol-Myers Squibb"
},
{
"label": "Verizon",
"value": "Verizon"
},
{
"label": "China Construction Bank",
"value": "China Construction Bank"
},
{
"label": "Texas Instruments",
"value": "Texas Instruments"
},
{
"label": "Linde",
"value": "Linde"
},
{
"label": "United Parcel Service",
"value": "United Parcel Service"
},
{
"label": "Adobe",
"value": "Adobe"
},
{
"label": "Nextera Energy",
"value": "Nextera Energy"
},
{
"label": "China Mobile",
"value": "China Mobile"
},
{
"label": "Tata Consultancy Services",
"value": "Tata Consultancy Services"
},
{
"label": "Nike",
"value": "Nike"
},
{
"label": "CATL",
"value": "CATL"
},
{
"label": "Agricultural Bank of China",
"value": "Agricultural Bank of China"
},
{
"label": "Amgen",
"value": "Amgen"
},
{
"label": "Comcast",
"value": "Comcast"
},
{
"label": "Morgan Stanley",
"value": "Morgan Stanley"
},
{
"label": "Hermès",
"value": "Hermès"
},
{
"label": "Philip Morris",
"value": "Philip Morris"
},
{
"label": "TotalEnergies",
"value": "TotalEnergies"
},
{
"label": "Charles Schwab",
"value": "Charles Schwab"
},
{
"label": "Raytheon Technologies",
"value": "Raytheon Technologies"
},
{
"label": "QUALCOMM",
"value": "QUALCOMM"
},
{
"label": "Netflix",
"value": "Netflix"
},
{
"label": "BHP Group",
"value": "BHP Group"
},
{
"label": "Royal Bank Of Canada",
"value": "Royal Bank Of Canada"
},
{
"label": "PetroChina",
"value": "PetroChina"
},
{
"label": "Honeywell",
"value": "Honeywell"
},
{
"label": "Elevance Health",
"value": "Elevance Health"
},
{
"label": "AT&T",
"value": "AT&T"
},
{
"label": "CVS Health",
"value": "CVS Health"
},
{
"label": "Lockheed Martin",
"value": "Lockheed Martin"
},
{
"label": "Intuit",
"value": "Intuit"
},
{
"label": "Union Pacific Corporation",
"value": "Union Pacific Corporation"
},
{
"label": "Bank of China",
"value": "Bank of China"
},
{
"label": "IBM",
"value": "IBM"
},
{
"label": "Deere & Company",
"value": "Deere & Company"
},
{
"label": "Toronto Dominion Bank",
"value": "Toronto Dominion Bank"
},
{
"label": "Lowe's Companies",
"value": "Lowe's Companies"
},
{
"label": "Unilever",
"value": "Unilever"
},
{
"label": "HDFC Bank",
"value": "HDFC Bank"
},
{
"label": "Goldman Sachs",
"value": "Goldman Sachs"
},
{
"label": "Intel",
"value": "Intel"
},
{
"label": "Medtronic",
"value": "Medtronic"
}
];
@api
validate() {
const inputFields = this.template.querySelectorAll('lightning-input');
const comboBox = this.template.querySelector('lightning-combobox');
const validity = {
isValid: true,
errorMessage: 'Please fill the required fields!'
};
inputFields.forEach(inputField => {
if(inputField.checkValidity()) {
this.leadRecord[inputField.name] = inputField.value;
} else {
validity.isValid = false;
}
});
if(comboBox.checkValidity()) {
this.leadRecord[comboBox.name] = comboBox.value;
} else {
validity.isValid = false;
}
return validity;
}
}</pre><div>We're not actually performing the callout here as our main task is to focus on data validation and pass data from lwc to screen flow in salesforce but you can think of it as a requirement and the solution which we've implemented for the same.</div><div><br /></div><h4 style="text-align: left;">validate()</h4><div>Let's discuss about our <b>validate()</b> method now. As you can see in the above code snippet, it's a public method with <b>@api</b> annotation. <b>This method will be called automatically when the user clicks on Next button of our screen flow</b>. You can perform any kind of validation here, for now, we'll just see if all the fields are filled or not. Another important part we're doing here is: <b>We'll populate the leadRecord property which will then be passed to our screen flow.</b></div><div><b><br /></b></div><div>If you see in the above code snippet, first of all, we got all the lightning input fields using <b>this.template.querySelectorAll('lightning-input')</b> and stored it in <b>inputFields</b> constant. Similarly, we queried the lightning combobox field using <b>this.template.querySelector('lightning-combobox')</b> and stored it in <b>comboBox</b> constant. Finally, we initialized a constant named <b>validity</b> with an object which has two properties <b>isValid</b> and <b>errorMessage</b>. This object will be returned by our validate method and can notify the flow that there's an error in LWC and the flow should not proceed ahead. We've initialized <b>isValid</b> to true, however it should be false if there is a validation error and the <b>errorMessage</b> that we provide here will be used by the flow to show a validation error on the flow screen. For our component, we've the error message as <b>Please fill the required fields!</b> as we'll show this error only if any of the required fields is not filled.</div><div><br /></div><div>Next we've iterated over all the input fields using a forEach loop and we've checked validity of each input field using the <b>checkValidity()</b> method. This method will return <b>true</b> if the input field is valid, otherwise, it'll return <b>false</b>. </div><div><br /></div><div>If the input field is valid, we're populating the lead record's property with the correct value as: <b>this.leadRecord[inputField.name] = inputField.value</b>. Notice by going back to the HTML section that each input field has a <b>name</b> attribute associated with it and the value of that name attribute is nothing but the API name of the actual salesforce field like: FirstName, LastName, Company. Therefore, here we're populating leadRecord['FirstName'] = <value of FirstName field> and leadRecord['LastName'] = <value of LastName field>.</div><div><br /></div><div>If the input field is not valid, we've setup <b>isValid</b> property of <b>validity</b> to <b>false</b> as: <b>validity.isValid = false</b>. </div><div><br /></div><div>A similar check is added for our lightning-combobox input as well. Finally, we returned our validity object. If you notice, we've done two things here:<br /><br /></div><div><ol style="text-align: left;"><li>Validated the input in our lwc using the <b>validate()</b> method and returned the proper output.<br /><br /></li><li>Populated the <b>leadRecord</b> object which will be passed to our screen flow</li></ol><div><br />Out JavaScript part is complete here. Let's move on to the meta file now:</div></div><h3 style="text-align: left;">createLead.js-meta.xml</h3><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__FlowScreen</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__FlowScreen">
<property name="title" type="String" label="Title" role="inputOnly"></property>
<property name="leadRecord" type="@salesforce/schema/Lead" label="Lead Record" role="outputOnly"></property>
</targetConfig>
</targetConfigs>
</LightningComponentBundle></pre><div>As you can see above, this is very much similar to the meta file that we added in our <a href="https://www.sfdcstop.com/2022/10/how-to-pass-data-from-screen-flow-to.html" target="_blank"><span style="color: #2b00fe;">previous tutorial</span></a>. We've added a target named <b>lightning__FlowScreen</b> so that the component can be used in flows. Under the configurations for this target, we have two properties:</div><div><br /></div><div><ol style="text-align: left;"><li><b>title:</b> The title of our lightning-card and will be passed by our flow to lwc. Notice that it's of type <b>String</b>. The label is <b>Title</b> which is fine and <b>role</b> is <b>inputOnly</b> as we just want to get this variable’s value from the screen flow as an input, we don't want to pass it's value back to the flow if it's updated in our lwc.<br /><br /></li><li><b>leadRecord</b>: The type of this property is <b>@salesforce/schema/Lead</b> because it'll store the actual salesforce lead record as per the inputs by the user. The label is <b>Lead Record</b> and the <b>role</b> for this property is <b>outputOnly</b>. This is because we'll be passing the value of this variable from LWC to flow. We don't want the admin to set it's value from flow and pass it to LWC.</li></ol><div><br /></div><div>So, that's all for our LWC. Let's move on to the screen flow now!</div></div><div><br /></div><h2 style="text-align: left;">Screen Flow: Create Lead</h2><div>Our screen flow looks like this:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8Hj0QmYqPcl5HCHTJmbhpabYYUVezm3SwMtn7RkDeeLo8Fx3YltFMHh9mFm-p-3FSaWt3Ej_Wp7XtQwl3YypXg_bdUNuyWsFmU8S_cCKzOywOvnOPo6BwHh245iSBAvqf6Prg7U8z7NXTG8StckDJYNH_j7zH3BHajgj5PkWA2_LcY4grgiHz-hZP/s1082/Screenshot%202022-10-25%20at%2010.36.32%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1082" data-original-width="818" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8Hj0QmYqPcl5HCHTJmbhpabYYUVezm3SwMtn7RkDeeLo8Fx3YltFMHh9mFm-p-3FSaWt3Ej_Wp7XtQwl3YypXg_bdUNuyWsFmU8S_cCKzOywOvnOPo6BwHh245iSBAvqf6Prg7U8z7NXTG8StckDJYNH_j7zH3BHajgj5PkWA2_LcY4grgiHz-hZP/w484-h640/Screenshot%202022-10-25%20at%2010.36.32%20PM.png" width="484" /></a></div><h3 style="text-align: left;">Variable: LeadRecord</h3>First of all, let's create a variable named <b>LeadRecord</b>. It should be of data type <b>Record</b> and object name should be <b>Lead</b> as shown below:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF-3EIyP3_gYrBFYiX3oEYKNvgGo9kAsNXIIrtg_I-t3VVuOjpxz46s3ysaUhz1wLj2tXQcxh-fHrbRK2HZbH2WHdTi_pLUzVxK7ZLqOuC4iHR0VeWDvn7HCMLVLfXmUA0FQ0NO40397z2a2TImJOPY2y3Vwyx0AkbiLOFcNldZ5Zrq4JvnGerOadE/s1712/Screenshot%202022-10-25%20at%2010.27.13%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="920" data-original-width="1712" height="344" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhF-3EIyP3_gYrBFYiX3oEYKNvgGo9kAsNXIIrtg_I-t3VVuOjpxz46s3ysaUhz1wLj2tXQcxh-fHrbRK2HZbH2WHdTi_pLUzVxK7ZLqOuC4iHR0VeWDvn7HCMLVLfXmUA0FQ0NO40397z2a2TImJOPY2y3Vwyx0AkbiLOFcNldZ5Zrq4JvnGerOadE/w640-h344/Screenshot%202022-10-25%20at%2010.27.13%20PM.png" width="640" /></a></div>This variable will be mapped to the <b>leadRecord</b> variable of our LWC. Click on <b>Done</b></div><h3 style="text-align: left;">Screen: Populate Lead Record</h3><div>In order to use it, let's configure our <b>Populate Lead Record</b> screen. This is where we'll embed our <b>createLead</b> LWC. Let's see what do we have in this:</div><div><div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVh8pzlYyrXWUMU6il4ZICiHfQQqackUyHKy8p3zf-RnO-TgAheNAO5GD4IjLYz82_Kte4e4TDHvzWvbGb0JRjpsJOouz0aCP6C2j1d9-Mzo7dTac5aYoJOuf_LY5O70Oa8OLMW15s-7KR3jBQS_NXf6LIXCzcBCzXz0ABtnyaYarTp2md4ZfOHtGL/s2650/Screenshot%202022-10-25%20at%2010.18.46%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1330" data-original-width="2650" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVh8pzlYyrXWUMU6il4ZICiHfQQqackUyHKy8p3zf-RnO-TgAheNAO5GD4IjLYz82_Kte4e4TDHvzWvbGb0JRjpsJOouz0aCP6C2j1d9-Mzo7dTac5aYoJOuf_LY5O70Oa8OLMW15s-7KR3jBQS_NXf6LIXCzcBCzXz0ABtnyaYarTp2md4ZfOHtGL/w640-h322/Screenshot%202022-10-25%20at%2010.18.46%20PM.png" width="640" /></a></div>As you can see above, on the left hand side, we can search for <b>createLead</b> lwc and it'll be available under the <b>Custom</b> section (because we've added <b>lightning__FlowScreen</b> in our LWC meta file). We can drag and drop this on our screen shown in the middle within our screenshot above. On the right hand side, we can specify the API name for this component like: <b>CreateLead</b> and the <b>Title</b>. This is the same title property that we marked as <b>inputOnly</b> in our <b>targetConfig</b>. This title will be displayed on our <b>lightning-card</b>. The title we entered here is: <b>Let's create a new Lead</b><br /><div><br /></div><div>Now, it's time to configure our <b>leadRecord</b> property of LWC's <b>targetConfig</b>. Under the <b>Advanced</b> settings, click <b>Manually assign variables</b> and you'll find an option to assign <b>Lead Record</b> property of LWC to a flow variable as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJIMeWswSMZ_E0ud7URDkC3Hsf8SCRb7sdA36ymU0A9bq-m82Fugy6i01sm-GlhBXBIANRyA8b5atJ7su09ryafug0me5NdPZKdptb97NloptdWmCPnRFSnUTr13candovQGi7wWq7U8ehIpJQj4gGNIWtz0IBjaZRxbYZHjfCKrTZSMkM28Y29gqG/s1362/Screenshot%202022-10-25%20at%2010.24.45%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1362" data-original-width="696" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJIMeWswSMZ_E0ud7URDkC3Hsf8SCRb7sdA36ymU0A9bq-m82Fugy6i01sm-GlhBXBIANRyA8b5atJ7su09ryafug0me5NdPZKdptb97NloptdWmCPnRFSnUTr13candovQGi7wWq7U8ehIpJQj4gGNIWtz0IBjaZRxbYZHjfCKrTZSMkM28Y29gqG/w328-h640/Screenshot%202022-10-25%20at%2010.24.45%20PM.png" width="328" /></a></div>As you can see above, we can choose our <b>LeadRecord</b> variable here that we created initially. This is where we're passing the output from our LWC to a screen flow variable. Isn't it so simple?<br /><div><br /></div><div>Make sure to populate <b>Screen Properties</b> with value <b>Populate Lead Record</b> as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq7jdQOL_F8HFU3jqJVXXJQ9SQOu4oWVDIPQCJTA_umPqpusYJ6spTnjZSD9bHwcvQxQGNNkEzwbVWlMuv8rkR_FTG7mjleTm4STmGDks6ULszG710r7l5DV0DP4rZZhLZiID9uIeie8OlpxgHz2AricoCAU_fAYqUHFUwJkJMOoSqCzGD9TzrQ5LZ/s2696/Screenshot%202022-10-25%20at%2010.37.55%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1356" data-original-width="2696" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq7jdQOL_F8HFU3jqJVXXJQ9SQOu4oWVDIPQCJTA_umPqpusYJ6spTnjZSD9bHwcvQxQGNNkEzwbVWlMuv8rkR_FTG7mjleTm4STmGDks6ULszG710r7l5DV0DP4rZZhLZiID9uIeie8OlpxgHz2AricoCAU_fAYqUHFUwJkJMOoSqCzGD9TzrQ5LZ/w640-h322/Screenshot%202022-10-25%20at%2010.37.55%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">Click on <b>Done</b></div><h3 style="text-align: left;">Assignment: Assign Default Values</h3>Now we have the lead record coming from LWC with FirstName, LastName and Company populated. Let's assign default values to our <b>LeadSource</b> and <b>Rating</b> as well using the <b>Assignment</b> component, as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuGGn4ft1aZLm1dlXuCUOo0r0FEif4NT6yq7S8Dt9X5LQPGg-1xPdDEwhLChSOhBGYR1KxWOWNltmjXy_rcfwKgboLnqeP45JhwZkyVtJ0sGujsuq5hGkvnxjNPqxilBKHuh2U4VoHuo1nYTABxEuh4OHUZIS47iTjzDQhZb4pUI-ht8cYpEdTMua2/s1600/Screenshot%202022-10-25%20at%2010.40.19%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="958" data-original-width="1600" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuGGn4ft1aZLm1dlXuCUOo0r0FEif4NT6yq7S8Dt9X5LQPGg-1xPdDEwhLChSOhBGYR1KxWOWNltmjXy_rcfwKgboLnqeP45JhwZkyVtJ0sGujsuq5hGkvnxjNPqxilBKHuh2U4VoHuo1nYTABxEuh4OHUZIS47iTjzDQhZb4pUI-ht8cYpEdTMua2/w640-h384/Screenshot%202022-10-25%20at%2010.40.19%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">Click on <b>Done</b></div><h3 style="text-align: left;">Create Records: Create Lead</h3>Finally, we'll use the <b>Create Records</b> component to create a new lead record using our <b>LeadRecord</b> variable as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkvlnS5VnJAFUvGCPsFJbEvIgiGCifIyivjNZjPQka636683wZd_0V05AYSFoD2C5vEwhsKQjFbq6iYZfPhfDZTORckOZEo1IGZ02Z5Vg1aAlSjdcZ6csqSwS5KMVGa06f1g-A31lCuLGIwck8PW5HGurZpUy4VMKyTOylqwzH5y3FJEybDXQXMBEM/s1630/Screenshot%202022-10-25%20at%2010.41.54%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1110" data-original-width="1630" height="436" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkvlnS5VnJAFUvGCPsFJbEvIgiGCifIyivjNZjPQka636683wZd_0V05AYSFoD2C5vEwhsKQjFbq6iYZfPhfDZTORckOZEo1IGZ02Z5Vg1aAlSjdcZ6csqSwS5KMVGa06f1g-A31lCuLGIwck8PW5HGurZpUy4VMKyTOylqwzH5y3FJEybDXQXMBEM/w640-h436/Screenshot%202022-10-25%20at%2010.41.54%20PM.png" width="640" /></a></div><div><br /></div>Click on <b>Done</b>. Now, it's time to save our new flow, let's name it as <b>Create Lead</b>:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP2OhhqZtoM6Qf1gDjVwdHYPy6lv0s56l2wiY2oZOcHCA7c-fpangE5geroFhTwZEqgpJn-yy1Lng87qpYp65gOKbkzwo5RtI6VPvvp-WNz_xEKPdUDg-EuqIudkJHTaHmfx7Z-5rS3ZcHnqkRYel14J-ZgGMqvj1QsNMWBjTUTHxoxkhBbxwtguD5/s2084/Screenshot%202022-10-25%20at%204.49.43%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1126" data-original-width="2084" height="346" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgP2OhhqZtoM6Qf1gDjVwdHYPy6lv0s56l2wiY2oZOcHCA7c-fpangE5geroFhTwZEqgpJn-yy1Lng87qpYp65gOKbkzwo5RtI6VPvvp-WNz_xEKPdUDg-EuqIudkJHTaHmfx7Z-5rS3ZcHnqkRYel14J-ZgGMqvj1QsNMWBjTUTHxoxkhBbxwtguD5/w640-h346/Screenshot%202022-10-25%20at%204.49.43%20PM.png" width="640" /></a></div><br /><div>Click on <b>Save</b>. Make sure to activate it as well, so the highlighted button below is showing <b>Deactivate</b>:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYmXy9_xZrSIKYv5XpNXU2EoSuAXIMTs3sVpBuRzrGaA3ZRfV6BW1Qoun-Hu9GCaBUH0GIPSVO8Vz5pN3x6oyzNnT_88viONQZXziJWSI7rg-SYesRrEV8ksH03Exv2kDvnLpe8FFWx4eDVN26uUshJZqwhv9SGejcAv_yiqgXtJk_cJubY8vFZZc4/s2574/Screenshot%202022-10-25%20at%2010.48.16%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="870" data-original-width="2574" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYmXy9_xZrSIKYv5XpNXU2EoSuAXIMTs3sVpBuRzrGaA3ZRfV6BW1Qoun-Hu9GCaBUH0GIPSVO8Vz5pN3x6oyzNnT_88viONQZXziJWSI7rg-SYesRrEV8ksH03Exv2kDvnLpe8FFWx4eDVN26uUshJZqwhv9SGejcAv_yiqgXtJk_cJubY8vFZZc4/w640-h216/Screenshot%202022-10-25%20at%2010.48.16%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">Our flow is complete!</div><h2 style="text-align: left;">Add Screen Flow to Homepage</h2><div>Now it's time to test our flow. Go to any lightning app (I am using the Sales app here). Click on the Edit Page section:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK5kLwr_Zqwbhip7ARU7ScX2DMO90g3lihVF2uqScU2Hr-QZZi4afiKB_iFRUkL0Chm0eszXXmdBXOUASEvP60-mvkeaH16Cg_EH9kbg2w27EmJEDMdBhK7f0wtM_6zfmSsNDvDeQgdMVwub6z4WClShMkykXCGMMxbmsDmf8S8XAdK9ls6sneh6Lx/s530/Screenshot%202022-10-25%20at%2010.55.04%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="396" data-original-width="530" height="478" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK5kLwr_Zqwbhip7ARU7ScX2DMO90g3lihVF2uqScU2Hr-QZZi4afiKB_iFRUkL0Chm0eszXXmdBXOUASEvP60-mvkeaH16Cg_EH9kbg2w27EmJEDMdBhK7f0wtM_6zfmSsNDvDeQgdMVwub6z4WClShMkykXCGMMxbmsDmf8S8XAdK9ls6sneh6Lx/w640-h478/Screenshot%202022-10-25%20at%2010.55.04%20PM.png" width="640" /></a></div>On the lightning page builder, follow the steps as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrTrBVL1CpKn8s8V-GdtyNjs72dO6nuvuMHxTPT34uQ5Ni_HOfLCQ39OcoQOwqJxiNfdLLDJwoEGn2DnsdT172_NGUUsmXA6VGBLMueJYu_vL-8QKQYn9jOe7LYgqKls-jpoA57Ls5PY3Dbol1RQav4TnUrPCAl8JjCUspAj06t-Q928_CNZPluza-/s2880/Screenshot%202022-10-25%20at%2010.51.02%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1128" data-original-width="2880" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrTrBVL1CpKn8s8V-GdtyNjs72dO6nuvuMHxTPT34uQ5Ni_HOfLCQ39OcoQOwqJxiNfdLLDJwoEGn2DnsdT172_NGUUsmXA6VGBLMueJYu_vL-8QKQYn9jOe7LYgqKls-jpoA57Ls5PY3Dbol1RQav4TnUrPCAl8JjCUspAj06t-Q928_CNZPluza-/w640-h250/Screenshot%202022-10-25%20at%2010.51.02%20PM.png" width="640" /></a></div><ol style="text-align: left;"><li>Search for <b>flow</b> in the left sidebar under <b>Components</b><br /><br /></li><li><b>Drag and Drop</b> the flow component on the lightning page<br /><br /></li><li>Choose the <b>Create Lead</b> flow from the Flow Configurations<br /><br /></li><li><b>Save</b> the page<br /><br /></li><li><b>Activate</b> the page (if not done before) and set it as org default as we did in our <a href="https://www.sfdcstop.com/2022/10/how-to-pass-data-from-screen-flow-to.html" target="_blank"><span style="color: #2b00fe;">previous tutorial</span></a>.</li></ol><div><br /></div><div>Once the flow is ready, you can populate the information of the lead as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZUEgKzeHFE95WmZ8ltajJ0-EiBxVh4H1YgimHyBltqXTg4pFUXUasBdvY5JfDA8uaQP9zvpebWLm3yd1HzzCoWsAOjDd8M48NubCivWXpTcn_UbRL2UCfRyHOIRVVwIUjFTmO9_HqCTmKaSbY3W27z01SDKQlNLHmfQ-YkFU43AZcC5mvYLhq5oAR/s1124/Screenshot%202022-10-25%20at%2010.59.49%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="888" data-original-width="1124" height="506" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZUEgKzeHFE95WmZ8ltajJ0-EiBxVh4H1YgimHyBltqXTg4pFUXUasBdvY5JfDA8uaQP9zvpebWLm3yd1HzzCoWsAOjDd8M48NubCivWXpTcn_UbRL2UCfRyHOIRVVwIUjFTmO9_HqCTmKaSbY3W27z01SDKQlNLHmfQ-YkFU43AZcC5mvYLhq5oAR/w640-h506/Screenshot%202022-10-25%20at%2010.59.49%20PM.png" width="640" /></a></div>As you click on <b>Next</b>, a new lead will be created in the system. You can check the <b>Lead</b> record and it'll have all the information populated as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjthMKs2u25YSofxdrmbSbfE1k-rJnKwKyVtmazhKblK0kZHY-dUTt56wavx_uD8ImMRXlT0AdfUZXXz4i4-Qt2pZwS1KOKkFMDCE6uUZFRdriz0FCHjPgQ5X9bUfxjYsc7BipyGfPYdeBIu-1W5dBE3xZboC7jpdh-LXwkkSeo9TsgouNnSnlBvYte/s2046/Screenshot%202022-10-25%20at%2011.01.03%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1642" data-original-width="2046" height="514" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjthMKs2u25YSofxdrmbSbfE1k-rJnKwKyVtmazhKblK0kZHY-dUTt56wavx_uD8ImMRXlT0AdfUZXXz4i4-Qt2pZwS1KOKkFMDCE6uUZFRdriz0FCHjPgQ5X9bUfxjYsc7BipyGfPYdeBIu-1W5dBE3xZboC7jpdh-LXwkkSeo9TsgouNnSnlBvYte/w640-h514/Screenshot%202022-10-25%20at%2011.01.03%20PM.png" width="640" /></a></div>As you can see above, we have the <b>Name (FirstName + LastName)</b>, <b>Company</b>, <b>Lead Source</b> and <b>Rating</b> populated as expected.<div><br /></div><div>In case you don't fill any fields in our screen flow and click <b>Next</b>, you'll have the <b>Error Message</b> visible on the screen as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHt5KOR9O09WQ4tpVdO9TEfwmj-plVhPtRMy3Qp8DCO_coWGnZIww8Tq76LS__FuDhPnnUiDpn9O1-HjQXNwDIQFVtfXaxlE8PHY2CTktTMLu0fyaoKm5mpVk81qjpKyTHTzlAvVWDwgYR6SxEQYlBeP9oXEk2rNAs-x2Ns_5ahCdCSXPMchYFY0UT/s1012/Screenshot%202022-10-25%20at%2011.03.32%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1010" data-original-width="1012" height="638" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHt5KOR9O09WQ4tpVdO9TEfwmj-plVhPtRMy3Qp8DCO_coWGnZIww8Tq76LS__FuDhPnnUiDpn9O1-HjQXNwDIQFVtfXaxlE8PHY2CTktTMLu0fyaoKm5mpVk81qjpKyTHTzlAvVWDwgYR6SxEQYlBeP9oXEk2rNAs-x2Ns_5ahCdCSXPMchYFY0UT/w640-h638/Screenshot%202022-10-25%20at%2011.03.32%20PM.png" width="640" /></a></div>This error message is coming from our <b>validate()</b> method of LWC and is displayed by the flow automatically.<br /><div><br /><div>That's all for this tutorial. I hope you liked it. Let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-78976462202238923972022-10-18T09:53:00.000-07:002022-10-18T09:53:22.303-07:00Getting started with Composite API in Salesforce<p>Hello Trailblazers,</p><p><br /></p><p>In this post we're going to learn about Composite Resources in salesforce. Composite resources can improve your application's performance by minimizing round trips between client and salesforce (server). Let's try to learn by an example.</p><p><br /></p><h2 style="text-align: left;">Get account details with related contacts and opportunities from Salesforce</h2><div>Let's consider this small requirement: You need to sync account records along with the related contacts and opportunities from Salesforce to an external system. Let's say you're syncing one account at a time using the account's id. You may think of a solution which consist of 3 steps:</div><div><br /></div><div><ol style="text-align: left;"><li>Get the account record using the account id by making a callout to standard Rest API</li><li>Get the contact records related to the account record by making another callout to the standard Rest API</li><li>Get the opportunity records related to the account record by making another callout to the standard Rest API</li></ol><div><br /></div><div>Therefore, you got your account record along with the related contacts and opportunities in 3 Rest API calls to salesforce. There can be other alternative approaches as well depending upon the use case and requirement. The goal here is to know about what's provided to us out of the box by salesforce and how can we leverage it.</div></div><h3 style="text-align: left;">Composite API in Action - Get Records</h3><div>What if I tell you: <b>You can fetch account, related contacts and related opportunities as well, all in a single API callout without writing any custom code?</b> Yes that's possible using the Composite API. Let's see how:</div><div><br /></div><div>I am not covering the client Authentication part (how to get access token from salesforce) in this blog as we've a full blog dedicated to it: <a href="https://www.sfdcstop.com/2019/01/how-to-connect-to-salesforce-with.html" style="font-weight: bold;" target="_blank"><span style="color: #2b00fe;">How to connect to Salesforce with Postman?</span></a></div><div><br /></div><div><b>Composite API</b> can execute a series of REST API requests (like: the 3 requests for our use case, which we're planning to execute one by one) in a single POST request. It can also retrieve a list of other composite resources with a GET request.</div><div><br /></div><div>You can send multiple REST API requests together using composite api and the thing to note here is: <b>The output of one request can be used as the input to a subsequent request</b>. All requests in a composite call are called subrequests and all subrequests are executed in the context of the same user who's calling the API. The good thing here is: <b>All the requests mentioned in the Composite API are sent together and they count as a single call towards your API limits</b>.</div><div><br /></div><div>Let's solve our use case now. We need to get Account record along with it's related Contacts and Opportunities and we only have the <b>account id</b> that we can pass to the request.</div><div><br /></div><div>This is my account record in salesforce along with the related records:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit721hQVeTrQb25GXt0QKpHbqTx9ELdB-iqe9z1Cj642b3wCtccoi-vbXrLlDG9wQnrKju4YjwAdYCeifG2aE8ubtfb_8qj_XBNvRRc1nemqEuX6hdX8u1ScP320UqRHO3oVIZKARucukpm1mdqmfRn9Bgsk1rexG8KoQJsXS_va9l2kPJGTGA6FHz/s1930/Screenshot%202022-10-16%20at%201.29.47%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1446" data-original-width="1930" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit721hQVeTrQb25GXt0QKpHbqTx9ELdB-iqe9z1Cj642b3wCtccoi-vbXrLlDG9wQnrKju4YjwAdYCeifG2aE8ubtfb_8qj_XBNvRRc1nemqEuX6hdX8u1ScP320UqRHO3oVIZKARucukpm1mdqmfRn9Bgsk1rexG8KoQJsXS_va9l2kPJGTGA6FHz/w640-h480/Screenshot%202022-10-16%20at%201.29.47%20PM.png" width="640" /></a></div><br /><div>As you can see above, the account's name is <b>Sample Account</b> and under that we have 2 Contact records: <b>Sample Contact 1</b> and <b>Sample Contact 2</b>. Similarly, we have 2 Opportunity records as well: <b>Sample Opportunity 1</b> and <b>Sample Opportunity 2</b>. We're going to fetch this whole information using composite API. Let's have a look at the request below:</div><pre>{
"compositeRequest": [
{
"method": "GET",
"url": "/services/data/v55.0/sobjects/Account/0016D00000fHjSLQA0",
"referenceId": "refAccount"
},
{
"method": "GET",
"url": "/services/data/v55.0/sobjects/Account/@{refAccount.Id}/Contacts",
"referenceId": "refContacts"
},
{
"method": "GET",
"url": "/services/data/v55.0/sobjects/Account/@{refAccount.Id}/Opportunities",
"referenceId": "refOpportunities"
}
]
}</pre><div>As you can see above, under the <b>compositeRequest</b> array, I've specified various requests that I want to make to salesforce. Below are the 3 requests I've specified:</div><div><br /></div><div><ol style="text-align: left;"><li><b>Get Account record using record id</b>: Here, I've specified record id of the account record in the request.<br /><br /></li><li><b>Get Contacts related to the current account</b>: Here, you can also specify the account id itself in the request body but I've referenced the response of previous request and used it as an input here. Our previous request will return the account record in the request body which I'm referring as <b>refAccount</b>. That record will have an <b>Id</b> key in the response body with the value as record id of the account. So, I've specified <b>@{refAccount.Id}</b> in the URL of the second request where we're fetching the Contacts related to the account. We've specified <b>refContacts</b> as the reference id for this API response.<br /><br /></li><li><b>Get Opportunities related to the current account</b>: This is exact similar to the 2nd request, the only difference is - instead of getting contacts, here we're getting the <b>Opportunities</b> related to the current account record. The reference id for this request's response is <b>refOpportunities</b>.</li></ol><div><br /></div><div>This is how my request looks like in postman:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW6Emkec2WXVTgjEMy-yxh398mbp41BJzp4ZUd2FuO6LakZyrPoAa2jQhQlqsk0yUINoHD_k6TVXtUZOoY6GggalKFEweV46-FKYBC-oxirbCqaFJxsCIl4eRUh9Ag5GqNNuL2bZrHhFtC_oWDFrt-KaqBhYfmFcMnWxo9uPo-G-HUBoBlKGUo5ZMK/s2032/Screenshot%202022-10-16%20at%201.42.56%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1060" data-original-width="2032" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW6Emkec2WXVTgjEMy-yxh398mbp41BJzp4ZUd2FuO6LakZyrPoAa2jQhQlqsk0yUINoHD_k6TVXtUZOoY6GggalKFEweV46-FKYBC-oxirbCqaFJxsCIl4eRUh9Ag5GqNNuL2bZrHhFtC_oWDFrt-KaqBhYfmFcMnWxo9uPo-G-HUBoBlKGUo5ZMK/w640-h334/Screenshot%202022-10-16%20at%201.42.56%20PM.png" width="640" /></a></div><br /><div>You can make a <b>POST</b> request and the URL for your API will be in this format: <b>https://<my-domain-name>.my.salesforce.com/services/data/vXX.X/composite</b></div></div><div><b><br /></b></div><div>Remember to add the <b>Authorization</b> header to the request with <b>Bearer<space><access token></b> as the value, that we got while doing <a href="https://www.sfdcstop.com/2019/01/how-to-connect-to-salesforce-with.html" target="_blank"><span style="color: #2b00fe;">authorization</span></a>. Let's have a look at the Authorization header below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7c4UsCU5ysVw9uX7oA6LLONUgs6hj-bRt0ZgOqtA-4StdszF7lDzY7u1znA_zO26eXlsPQt6iLIYAz3aiCzPPBtAdDGtJzfnwQFyq3WwbpakYMHTfAoJz3hTSFliP740dhWt1faAP-D64ZbBg_v1mClZFA7YDhT8LQuPx-YPQFhJrFMzr5m6ncY5J/s2050/Screenshot%202022-10-16%20at%201.46.06%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="482" data-original-width="2050" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7c4UsCU5ysVw9uX7oA6LLONUgs6hj-bRt0ZgOqtA-4StdszF7lDzY7u1znA_zO26eXlsPQt6iLIYAz3aiCzPPBtAdDGtJzfnwQFyq3WwbpakYMHTfAoJz3hTSFliP740dhWt1faAP-D64ZbBg_v1mClZFA7YDhT8LQuPx-YPQFhJrFMzr5m6ncY5J/w640-h150/Screenshot%202022-10-16%20at%201.46.06%20PM.png" width="640" /></a></div><br /><div>As you click on <b>Send</b> you'll get the response similar to what's shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoKN2UFYrKhRZEFululCouHsI0h3RJcZgctHQtLKUllnkr-j6815TLlt_zM3n_rClfHola5hm9xf4sKMrZQ7ENYEmjEphcDStaSXCIEUsFE_LNApa9AHrUGk7TdbShagBwHqRgXmW-vFrsCb2BojguP5ESsUKWPyvQBpMLOWhCr_MH1ci6FMp3wOU1/s2054/Screenshot%202022-10-16%20at%201.49.57%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="920" data-original-width="2054" height="286" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoKN2UFYrKhRZEFululCouHsI0h3RJcZgctHQtLKUllnkr-j6815TLlt_zM3n_rClfHola5hm9xf4sKMrZQ7ENYEmjEphcDStaSXCIEUsFE_LNApa9AHrUGk7TdbShagBwHqRgXmW-vFrsCb2BojguP5ESsUKWPyvQBpMLOWhCr_MH1ci6FMp3wOU1/w640-h286/Screenshot%202022-10-16%20at%201.49.57%20PM.png" width="640" /></a></div><br /><div>I am also sharing the full response below for your reference:</div><pre>{
"compositeResponse": [
{
"body": {
"attributes": {
"type": "Account",
"url": "/services/data/v55.0/sobjects/Account/0016D00000fHjSLQA0"
},
"Id": "0016D00000fHjSLQA0",
"IsDeleted": false,
"MasterRecordId": null,
"Name": "Sample Account",
"Type": null,
"ParentId": null,
"BillingStreet": null,
"BillingCity": null,
"BillingState": null,
"BillingPostalCode": null,
"BillingCountry": null,
"BillingLatitude": null,
"BillingLongitude": null,
"BillingGeocodeAccuracy": null,
"BillingAddress": null,
"ShippingStreet": null,
"ShippingCity": null,
"ShippingState": null,
"ShippingPostalCode": null,
"ShippingCountry": null,
"ShippingLatitude": null,
"ShippingLongitude": null,
"ShippingGeocodeAccuracy": null,
"ShippingAddress": null,
"Phone": null,
"Fax": null,
"AccountNumber": null,
"Website": null,
"PhotoUrl": "/services/images/photo/0016D00000fHjSLQA0",
"Sic": null,
"Industry": null,
"AnnualRevenue": null,
"NumberOfEmployees": null,
"Ownership": null,
"TickerSymbol": null,
"Description": null,
"Rating": null,
"Site": null,
"OwnerId": "0056D000005v9kXQAQ",
"CreatedDate": "2022-10-15T08:29:16.000+0000",
"CreatedById": "0056D000005v9kXQAQ",
"LastModifiedDate": "2022-10-15T08:29:16.000+0000",
"LastModifiedById": "0056D000005v9kXQAQ",
"SystemModstamp": "2022-10-15T08:29:16.000+0000",
"LastActivityDate": null,
"LastViewedDate": "2022-10-16T07:55:17.000+0000",
"LastReferencedDate": "2022-10-16T07:55:17.000+0000",
"Jigsaw": null,
"JigsawCompanyId": null,
"CleanStatus": "Pending",
"AccountSource": null,
"DunsNumber": null,
"Tradestyle": null,
"NaicsCode": null,
"NaicsDesc": null,
"YearStarted": null,
"SicDesc": null,
"DandbCompanyId": null,
"OperatingHoursId": null
},
"httpHeaders": {
"ETag": "\"gTI7lF2oYMlDxM+gTW1a62nzqxfxxihRqAygUNh9DPs=\"",
"Last-Modified": "Sat, 15 Oct 2022 08:29:16 GMT"
},
"httpStatusCode": 200,
"referenceId": "refAccount"
},
{
"body": {
"totalSize": 2,
"done": true,
"records": [
{
"attributes": {
"type": "Contact",
"url": "/services/data/v55.0/sobjects/Contact/0036D00000UAXTNQA5"
},
"Id": "0036D00000UAXTNQA5",
"IsDeleted": false,
"MasterRecordId": null,
"AccountId": "0016D00000fHjSLQA0",
"LastName": "Contact 1",
"FirstName": "Sample",
"Salutation": "Mr.",
"Name": "Sample Contact 1",
"OtherStreet": null,
"OtherCity": null,
"OtherState": null,
"OtherPostalCode": null,
"OtherCountry": null,
"OtherLatitude": null,
"OtherLongitude": null,
"OtherGeocodeAccuracy": null,
"OtherAddress": null,
"MailingStreet": null,
"MailingCity": null,
"MailingState": null,
"MailingPostalCode": null,
"MailingCountry": null,
"MailingLatitude": null,
"MailingLongitude": null,
"MailingGeocodeAccuracy": null,
"MailingAddress": null,
"Phone": null,
"Fax": null,
"MobilePhone": null,
"HomePhone": null,
"OtherPhone": null,
"AssistantPhone": null,
"ReportsToId": null,
"Email": null,
"Title": null,
"Department": null,
"AssistantName": null,
"LeadSource": null,
"Birthdate": null,
"Description": null,
"OwnerId": "0056D000005v9kXQAQ",
"CreatedDate": "2022-10-15T08:29:16.000+0000",
"CreatedById": "0056D000005v9kXQAQ",
"LastModifiedDate": "2022-10-16T07:46:44.000+0000",
"LastModifiedById": "0056D000005v9kXQAQ",
"SystemModstamp": "2022-10-16T07:46:44.000+0000",
"LastActivityDate": null,
"LastCURequestDate": null,
"LastCUUpdateDate": null,
"LastViewedDate": "2022-10-16T07:46:44.000+0000",
"LastReferencedDate": "2022-10-16T07:46:44.000+0000",
"EmailBouncedReason": null,
"EmailBouncedDate": null,
"IsEmailBounced": false,
"PhotoUrl": "/services/images/photo/0036D00000UAXTNQA5",
"Jigsaw": null,
"JigsawContactId": null,
"CleanStatus": "Pending",
"IndividualId": null
},
{
"attributes": {
"type": "Contact",
"url": "/services/data/v55.0/sobjects/Contact/0036D00000ULNcUQAX"
},
"Id": "0036D00000ULNcUQAX",
"IsDeleted": false,
"MasterRecordId": null,
"AccountId": "0016D00000fHjSLQA0",
"LastName": "Contact 2",
"FirstName": "Sample",
"Salutation": "Ms.",
"Name": "Sample Contact 2",
"OtherStreet": null,
"OtherCity": null,
"OtherState": null,
"OtherPostalCode": null,
"OtherCountry": null,
"OtherLatitude": null,
"OtherLongitude": null,
"OtherGeocodeAccuracy": null,
"OtherAddress": null,
"MailingStreet": null,
"MailingCity": null,
"MailingState": null,
"MailingPostalCode": null,
"MailingCountry": null,
"MailingLatitude": null,
"MailingLongitude": null,
"MailingGeocodeAccuracy": null,
"MailingAddress": null,
"Phone": null,
"Fax": null,
"MobilePhone": null,
"HomePhone": null,
"OtherPhone": null,
"AssistantPhone": null,
"ReportsToId": null,
"Email": null,
"Title": null,
"Department": null,
"AssistantName": null,
"LeadSource": null,
"Birthdate": null,
"Description": null,
"OwnerId": "0056D000005v9kXQAQ",
"CreatedDate": "2022-10-16T07:46:25.000+0000",
"CreatedById": "0056D000005v9kXQAQ",
"LastModifiedDate": "2022-10-16T07:46:25.000+0000",
"LastModifiedById": "0056D000005v9kXQAQ",
"SystemModstamp": "2022-10-16T07:46:25.000+0000",
"LastActivityDate": null,
"LastCURequestDate": null,
"LastCUUpdateDate": null,
"LastViewedDate": "2022-10-16T07:46:26.000+0000",
"LastReferencedDate": "2022-10-16T07:46:26.000+0000",
"EmailBouncedReason": null,
"EmailBouncedDate": null,
"IsEmailBounced": false,
"PhotoUrl": "/services/images/photo/0036D00000ULNcUQAX",
"Jigsaw": null,
"JigsawContactId": null,
"CleanStatus": "Pending",
"IndividualId": null
}
]
},
"httpHeaders": {},
"httpStatusCode": 200,
"referenceId": "refContacts"
},
{
"body": {
"totalSize": 2,
"done": true,
"records": [
{
"attributes": {
"type": "Opportunity",
"url": "/services/data/v55.0/sobjects/Opportunity/0066D000005z3tpQAA"
},
"Id": "0066D000005z3tpQAA",
"IsDeleted": false,
"AccountId": "0016D00000fHjSLQA0",
"IsPrivate": false,
"Name": "Sample Opportunity 1",
"Description": null,
"StageName": "Prospecting",
"Amount": null,
"Probability": 10.0,
"ExpectedRevenue": null,
"TotalOpportunityQuantity": null,
"CloseDate": "2022-10-17",
"Type": null,
"NextStep": null,
"LeadSource": null,
"IsClosed": false,
"IsWon": false,
"ForecastCategory": "Pipeline",
"ForecastCategoryName": "Pipeline",
"CampaignId": null,
"HasOpportunityLineItem": false,
"Pricebook2Id": null,
"OwnerId": "0056D000005v9kXQAQ",
"CreatedDate": "2022-10-16T07:29:30.000+0000",
"CreatedById": "0056D000005v9kXQAQ",
"LastModifiedDate": "2022-10-16T07:47:09.000+0000",
"LastModifiedById": "0056D000005v9kXQAQ",
"SystemModstamp": "2022-10-16T07:47:09.000+0000",
"LastActivityDate": null,
"PushCount": 0,
"LastStageChangeDate": null,
"FiscalQuarter": 4,
"FiscalYear": 2022,
"Fiscal": "2022 4",
"ContactId": null,
"LastViewedDate": "2022-10-16T07:47:09.000+0000",
"LastReferencedDate": "2022-10-16T07:47:09.000+0000",
"HasOpenActivity": false,
"HasOverdueTask": false,
"LastAmountChangedHistoryId": null,
"LastCloseDateChangedHistoryId": null
},
{
"attributes": {
"type": "Opportunity",
"url": "/services/data/v55.0/sobjects/Opportunity/0066D000005z3wPQAQ"
},
"Id": "0066D000005z3wPQAQ",
"IsDeleted": false,
"AccountId": "0016D00000fHjSLQA0",
"IsPrivate": false,
"Name": "Sample Opportunity 2",
"Description": null,
"StageName": "Prospecting",
"Amount": null,
"Probability": 10.0,
"ExpectedRevenue": null,
"TotalOpportunityQuantity": null,
"CloseDate": "2022-10-17",
"Type": null,
"NextStep": null,
"LeadSource": null,
"IsClosed": false,
"IsWon": false,
"ForecastCategory": "Pipeline",
"ForecastCategoryName": "Pipeline",
"CampaignId": null,
"HasOpportunityLineItem": false,
"Pricebook2Id": null,
"OwnerId": "0056D000005v9kXQAQ",
"CreatedDate": "2022-10-16T07:47:01.000+0000",
"CreatedById": "0056D000005v9kXQAQ",
"LastModifiedDate": "2022-10-16T07:47:01.000+0000",
"LastModifiedById": "0056D000005v9kXQAQ",
"SystemModstamp": "2022-10-16T07:47:01.000+0000",
"LastActivityDate": null,
"PushCount": 0,
"LastStageChangeDate": null,
"FiscalQuarter": 4,
"FiscalYear": 2022,
"Fiscal": "2022 4",
"ContactId": null,
"LastViewedDate": "2022-10-16T07:47:01.000+0000",
"LastReferencedDate": "2022-10-16T07:47:01.000+0000",
"HasOpenActivity": false,
"HasOverdueTask": false,
"LastAmountChangedHistoryId": null,
"LastCloseDateChangedHistoryId": null
}
]
},
"httpHeaders": {},
"httpStatusCode": 200,
"referenceId": "refOpportunities"
}
]
}</pre><div>Notice that in the <b>compositeResponse</b> array, we have 3 objects:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilSnL-HTc-2tE_oNrTU57Ksh-cIKlkRlVDxzF8mQByosFqoAeAjZW3063e5jSSg6hFJe8t2792MHPBES_dlU3rZkfWzEMUhw1ai5555tEnjgoX2Hs7cCEg1w2Lk4LBbbHf6v3tXhDaZzRwEEoA4ZLxyIWk2wUcoW0oxzsQuRagwFUldrBgsavWXAqS/s1086/Screenshot%202022-10-16%20at%201.58.41%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="568" data-original-width="1086" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEilSnL-HTc-2tE_oNrTU57Ksh-cIKlkRlVDxzF8mQByosFqoAeAjZW3063e5jSSg6hFJe8t2792MHPBES_dlU3rZkfWzEMUhw1ai5555tEnjgoX2Hs7cCEg1w2Lk4LBbbHf6v3tXhDaZzRwEEoA4ZLxyIWk2wUcoW0oxzsQuRagwFUldrBgsavWXAqS/w640-h334/Screenshot%202022-10-16%20at%201.58.41%20PM.png" width="640" /></a></div>The first object is the response from callout to the URL mentioned in the first object of compositeRequest array, in our request body, which is returning the account record. This API response has a body attribute which corresponds to our account record referred by <b>refAccount</b> as shown below:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioBIwq1tx0t-H0qEJX7tIVRMGEXm2Knqv27ml4t4olcePU6qwpTrT06xcbQUGbNX9o0MmzMtZXTEye00gJjUXacOQQuPpiwMkPGNzlN8J5IMqOxZ6uW7PCYsAlciRICr2BN6rbCdJxnxLGSYNMgTYojKMUHisV1q4dAEIN_LnfWGHnX8iYQp0Q-tOI/s930/Screenshot%202022-10-16%20at%202.01.46%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="790" data-original-width="930" height="544" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioBIwq1tx0t-H0qEJX7tIVRMGEXm2Knqv27ml4t4olcePU6qwpTrT06xcbQUGbNX9o0MmzMtZXTEye00gJjUXacOQQuPpiwMkPGNzlN8J5IMqOxZ6uW7PCYsAlciRICr2BN6rbCdJxnxLGSYNMgTYojKMUHisV1q4dAEIN_LnfWGHnX8iYQp0Q-tOI/w640-h544/Screenshot%202022-10-16%20at%202.01.46%20PM.png" width="640" /></a></div>The second object is the response from URL mentioned in the second object of our compositeRequest array, in our request body, which is returning the contact records related to this account. As you can see below, there are 2 contact records linked to the current account:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGs5-OCBmQJAEo4prFuPPe_s1F5t42Qpc6GdKOI568IPS4oD2EtzFkjBKlmfqPmr0C9kJfKCfE1ioZLA8_QctC_n14ZTPq5MjDAHJIbjN5xiNOxzQBFpicq2SYgHFD7kY-IHkBaJToqyt7QRPTH4PN6M7cLqaCPgv8yJHS8kb6uLpE_XmuafiEaFMq/s948/Screenshot%202022-10-16%20at%202.03.03%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="856" data-original-width="948" height="578" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGs5-OCBmQJAEo4prFuPPe_s1F5t42Qpc6GdKOI568IPS4oD2EtzFkjBKlmfqPmr0C9kJfKCfE1ioZLA8_QctC_n14ZTPq5MjDAHJIbjN5xiNOxzQBFpicq2SYgHFD7kY-IHkBaJToqyt7QRPTH4PN6M7cLqaCPgv8yJHS8kb6uLpE_XmuafiEaFMq/w640-h578/Screenshot%202022-10-16%20at%202.03.03%20PM.png" width="640" /></a></div><br /><div>We're referring to the body of this response using <b>refContacts</b>. Similarly, the third object is the response from the URL mentioned in the third object of our compositeRequest array, in our request body, which is returning the opportunities related to this account. You can see a similar response as above for opportunities below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj32z-eLK6LStacRgSocOPQQnIloSIP3-eMegIdtCP3kzfKywNcXyNNF5OUeyYQG4SCndXTyePu-CIr5o3p5AJnKWenIdMJyexPIlXjzY6vuuun70U5Dqizv4OOSRynTh2eJXfLAVZYc8mz7idJ6F0Fysl_tMLzu0WuwxnUpQXODqqx1judPP6Rudjv/s990/Screenshot%202022-10-16%20at%202.09.11%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="902" data-original-width="990" height="584" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj32z-eLK6LStacRgSocOPQQnIloSIP3-eMegIdtCP3kzfKywNcXyNNF5OUeyYQG4SCndXTyePu-CIr5o3p5AJnKWenIdMJyexPIlXjzY6vuuun70U5Dqizv4OOSRynTh2eJXfLAVZYc8mz7idJ6F0Fysl_tMLzu0WuwxnUpQXODqqx1judPP6Rudjv/w640-h584/Screenshot%202022-10-16%20at%202.09.11%20PM.png" width="640" /></a></div>We're referring to the body of this response as <b>refOpportunities</b>.<div><br /></div><div>So, this is how you can make a simple callout to Composite API to get your account, it's related contacts as well as it's related opportunities in one go!<br /><div><br /></div><div>Till now, we talked about how we can get the parent record and it's child records together using composite resources. You may ask: <b>What if I have a lookup field and I want to get that related record details as well?</b> - You can easily do that using composite API!</div><div><br /></div><div>For example: Le'ts say I want to get the account owner's information as well i.e. the user record related to the account using composite API while I am fetching the account record. To do that, I can just add the below object to my <b>compositeRequest</b> array in the request body:</div><pre>{
"method": "GET",
"url": "/services/data/v55.0/sobjects/User/@{refAccount.OwnerId}",
"referenceId": "refUser"
}</pre><div>As you can see above, I am referring to the <b>OwnerId</b> of my account record using <b>@{refAccount.OwnerId}</b> and I am getting the details from the <b>User</b> object using this OwnerId. The overall request is shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK83fkuS-3W3zxTFXaxeM0e1oWh2rKHrbTxcexzwoyCbcVoLUvJWAKuVIoQ8H3QE30izpiamVBjGd3r90Gc-Sec4D-hnAJDOMtRSvfImXjq4kQyfuu58t2O4bcY13noLN0ws0Uy8682NKm-K-arqscmXzf8WIfZH3vN9I-omFQ8FtLjiwsb4l5-aDR/s2050/Screenshot%202022-10-16%20at%203.57.29%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1222" data-original-width="2050" height="382" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK83fkuS-3W3zxTFXaxeM0e1oWh2rKHrbTxcexzwoyCbcVoLUvJWAKuVIoQ8H3QE30izpiamVBjGd3r90Gc-Sec4D-hnAJDOMtRSvfImXjq4kQyfuu58t2O4bcY13noLN0ws0Uy8682NKm-K-arqscmXzf8WIfZH3vN9I-omFQ8FtLjiwsb4l5-aDR/w640-h382/Screenshot%202022-10-16%20at%203.57.29%20PM.png" width="640" /></a></div>I am referring to the body of user API output i.e. the user record as <b>refUser</b>. In the response also, we'll get the user details at the end as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb69ozfIFgHhNKDLdoP8eiVWuLP6i1ONobX25PcKBXagG5IIt8dLl548OLz-PeHVWBx4gcy1kUt6aGHRZNr2ZMtdk_lKXFuKfgS9l6tpDTwATydn96cVRUW2nxlydNHS1zqzVmRucGMayLpvByIfPZE2-4RiokzEmkpSQ8GDO4vrMyCpNAd-U1MfQu/s2026/Screenshot%202022-10-16%20at%203.59.25%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1476" data-original-width="2026" height="466" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb69ozfIFgHhNKDLdoP8eiVWuLP6i1ONobX25PcKBXagG5IIt8dLl548OLz-PeHVWBx4gcy1kUt6aGHRZNr2ZMtdk_lKXFuKfgS9l6tpDTwATydn96cVRUW2nxlydNHS1zqzVmRucGMayLpvByIfPZE2-4RiokzEmkpSQ8GDO4vrMyCpNAd-U1MfQu/w640-h466/Screenshot%202022-10-16%20at%203.59.25%20PM.png" width="640" /></a></div>So, all you need to understand here is: <b>How to refer the previous response variables in the subsequent requests to fetch related records?</b> Once you understood this, you can easily use composite API for your use case. You can easily reference the previous API response body using the <b>reference id</b> you have specified in the request.<div><br /></div><div>Here we had 4 subrequests in a single composite API callout. We can have a maximum of <b>25 subrequests</b> in a single call. Out of these 25, 5 requests can be related to query operations or sObject collections.</div><div><h3 style="text-align: left;">Creating Records using Composite API</h3><div><div>Before marking this blog post as complete. I would like to show you one example of creating related records using composite API. This time we're going to create one account record, two opportunities and two contacts related to it using composite api. Let's have a look at the request body below:</div><pre>{
"compositeRequest": [
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Account",
"referenceId": "refAccount",
"body": {
"Name": "My Sample Account"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Contact",
"referenceId": "refContact1",
"body": {
"FirstName": "My Sample",
"LastName": "Contact 1",
"AccountId": "@{refAccount.id}"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Contact",
"referenceId": "refContact2",
"body": {
"FirstName": "My Sample",
"LastName": "Contact 2",
"AccountId": "@{refAccount.id}"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Opportunity",
"referenceId": "refOpportunity1",
"body": {
"Name": "My Sample Opportunity 1",
"AccountId": "@{refAccount.id}",
"ContactId": "@{refContact1.id}",
"StageName": "Prospecting",
"CloseDate": "2022-10-20"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Opportunity",
"referenceId": "refOpportunity2",
"body": {
"Name": "My Sample Opportunity 2",
"AccountId": "@{refAccount.id}",
"ContactId": "@{refContact2.id}",
"StageName": "Qualification",
"CloseDate": "2022-10-20"
}
}
]
}</pre><div>As you can see above, this time, the method for each request mentioned in the compositeRequest array is <b>POST</b> because we're creating records here. We also have a body in each request with the data we want to store in the record we're creating. First of all we created an account record with the name <b>My Sample Account</b> referred by <b>refAccount</b>, then we created two contact records: <b>My Sample Contact 1</b> referred by <b>refContact1</b> and <b>My Sample Contact 2</b> referred by <b>refContact2</b> under that account by mentioning the <b>AccountId</b> as <b>@{refAccount.id}</b>. </div><div><br /></div><div>Finally, we created two opportunity records as well named: <b>My Sample Opportunity 1</b> referred by <b>refOpportunity1 </b>and <b>My Sample Opportunity 2</b> referred by <b>refOpportunity2</b>. Notice that both the opportunity records are linked to the same account using: <b>@{refAccount.id}</b> as the <b>AccountId </b>but different contacts as the first one is referring to <b>@{refContact1.id}</b> as the <b>ContactId</b>, however the second one is using <b>@{refContact2.id}</b> as the <b>ContactId</b>. Value for StageName is also different for both the opportunity records.</div><div><br /></div><div>Below is the response for this API callout:</div><pre>{
"compositeResponse": [
{
"body": {
"id": "0016D00000fSq4HQAS",
"success": true,
"errors": []
},
"httpHeaders": {
"Location": "/services/data/v55.0/sobjects/Account/0016D00000fSq4HQAS"
},
"httpStatusCode": 201,
"referenceId": "refAccount"
},
{
"body": {
"id": "0036D00000ULNmoQAH",
"success": true,
"errors": []
},
"httpHeaders": {
"Location": "/services/data/v55.0/sobjects/Contact/0036D00000ULNmoQAH"
},
"httpStatusCode": 201,
"referenceId": "refContact1"
},
{
"body": {
"id": "0036D00000ULNmtQAH",
"success": true,
"errors": []
},
"httpHeaders": {
"Location": "/services/data/v55.0/sobjects/Contact/0036D00000ULNmtQAH"
},
"httpStatusCode": 201,
"referenceId": "refContact2"
},
{
"body": {
"id": "0066D000005z4LPQAY",
"success": true,
"errors": []
},
"httpHeaders": {
"Location": "/services/data/v55.0/sobjects/Opportunity/0066D000005z4LPQAY"
},
"httpStatusCode": 201,
"referenceId": "refOpportunity1"
},
{
"body": {
"id": "0066D000005z4LQQAY",
"success": true,
"errors": []
},
"httpHeaders": {
"Location": "/services/data/v55.0/sobjects/Opportunity/0066D000005z4LQQAY"
},
"httpStatusCode": 201,
"referenceId": "refOpportunity2"
}
]
}</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5DfyoBLcrXe1DqarfFAkfx7ZkKeGwFHACymlo1jg6pZpJqpP1Vtrg9SQ-C8FmOku-HYjXIHYRkAYFjo7b16NSj4nPdAlZQkxWy2SAMxVVOCeK3mrK8mizZRF-SgKjv8idEVYEkFdV_FLsTTicuLvk_b7UvxvmzYzH_f1MDhzJ_RHX6FR2DU_BJ_8t/s2050/Screenshot%202022-10-16%20at%204.38.03%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1476" data-original-width="2050" height="460" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5DfyoBLcrXe1DqarfFAkfx7ZkKeGwFHACymlo1jg6pZpJqpP1Vtrg9SQ-C8FmOku-HYjXIHYRkAYFjo7b16NSj4nPdAlZQkxWy2SAMxVVOCeK3mrK8mizZRF-SgKjv8idEVYEkFdV_FLsTTicuLvk_b7UvxvmzYzH_f1MDhzJ_RHX6FR2DU_BJ_8t/w640-h460/Screenshot%202022-10-16%20at%204.38.03%20PM.png" width="640" /></a></div><div>Let's verify our results in our salesforce org as well!</div><div><br /></div><div>We have an account record named <b>My Sample Account</b> with two contacts and two opportunities linked to it as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPLydHcPcdHF4ATdEJ6JruPL4_KSJITir1xOit-d45ISfJBKKlkoOXmcuPIcraBo6IK8Jynws2AytUL6RibLKekFvQanosVL5xvuu8T5a4hBdHxWnlYthyOJ2izwkwbwb7xYOQ4QqWdvWONDOEy7olpTRnXg_-Y7aX2Fa5cgi3fCVUtBu2L7lvzviA/s1930/Screenshot%202022-10-16%20at%204.39.01%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1556" data-original-width="1930" height="516" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPLydHcPcdHF4ATdEJ6JruPL4_KSJITir1xOit-d45ISfJBKKlkoOXmcuPIcraBo6IK8Jynws2AytUL6RibLKekFvQanosVL5xvuu8T5a4hBdHxWnlYthyOJ2izwkwbwb7xYOQ4QqWdvWONDOEy7olpTRnXg_-Y7aX2Fa5cgi3fCVUtBu2L7lvzviA/w640-h516/Screenshot%202022-10-16%20at%204.39.01%20PM.png" width="640" /></a></div>Notice the contact and opportunity names, the stage and close date of the opportunity records, they're the exact same as we mentioned in the request. You can open the opportunities and verify that both <b>My Sample Opportunity 1</b> and <b>My Sample Opportunity 2</b> are linked with <b>My Sample Contact 1</b> and <b>My Sample Contact 2</b> respectively as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkszENsGRJ0qli28M3VwWORISrtw3K94cvsW5uc-GKfrUs4WZw1hn66zo69-DDSaEBSbbwzfIvBH0OzlCfHA-jMDL4zeQhsGEXMnSfLZzMs-J710OOw3CZ2-IW6SMbYnGjRb9K0yDqcmv4feNSGkcCthDDHUjBLQuVK94deM1o8enIxxdK9eFsElse/s2880/Screenshot%202022-10-16%20at%204.40.46%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1556" data-original-width="2880" height="346" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkszENsGRJ0qli28M3VwWORISrtw3K94cvsW5uc-GKfrUs4WZw1hn66zo69-DDSaEBSbbwzfIvBH0OzlCfHA-jMDL4zeQhsGEXMnSfLZzMs-J710OOw3CZ2-IW6SMbYnGjRb9K0yDqcmv4feNSGkcCthDDHUjBLQuVK94deM1o8enIxxdK9eFsElse/w640-h346/Screenshot%202022-10-16%20at%204.40.46%20PM.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaoqAA2o5bomhXS6cq0TnepEQ5DzdxFA2PsF7q0bcJTKi6Wp0_Q6jle6FmZDnlhDQAiIvrRjOiW3Q16TMDuL7-uLb-F-PWdQBp6uUXOmTEpOqOUEYzOYE38VARGfr7zWxzTQd8iP_IT9gDvaZKs2CmYlTGzkg175r_sG6voQz3erzAcsv96nRcq11J/s2880/Screenshot%202022-10-16%20at%204.41.08%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1504" data-original-width="2880" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaoqAA2o5bomhXS6cq0TnepEQ5DzdxFA2PsF7q0bcJTKi6Wp0_Q6jle6FmZDnlhDQAiIvrRjOiW3Q16TMDuL7-uLb-F-PWdQBp6uUXOmTEpOqOUEYzOYE38VARGfr7zWxzTQd8iP_IT9gDvaZKs2CmYlTGzkg175r_sG6voQz3erzAcsv96nRcq11J/w640-h334/Screenshot%202022-10-16%20at%204.41.08%20PM.png" width="640" /></a></div><br /><div>That's how we can <b>Create Related Records using Composite API</b>. Till now, we're making a POST request to the composite API. There are some other composite resources as well, that we can use and we can get a list of those by making a GET request to the composite API as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyDvpSe9qYOEpvQkNwNGx41YaOz2sAqCWPAzspbrCZ6vxOoVAxyx9wpRxxzjsbV2iD-TnbVz6ZIGrS3T3_Pe7F4Ep1WC5GuwPtgIGvXk8t_2VM2rYXK3m15_A6N6Psli9ccSJrYpwTf1G-5_uq-w6T66W3KDlPGP93FrBrBILQlKQ-8iWOcuYCD6Jz/s2040/Screenshot%202022-10-16%20at%204.49.54%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1474" data-original-width="2040" height="462" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyDvpSe9qYOEpvQkNwNGx41YaOz2sAqCWPAzspbrCZ6vxOoVAxyx9wpRxxzjsbV2iD-TnbVz6ZIGrS3T3_Pe7F4Ep1WC5GuwPtgIGvXk8t_2VM2rYXK3m15_A6N6Psli9ccSJrYpwTf1G-5_uq-w6T66W3KDlPGP93FrBrBILQlKQ-8iWOcuYCD6Jz/w640-h462/Screenshot%202022-10-16%20at%204.49.54%20PM.png" width="640" /></a></div>I think the information we learned today should be enough for you to get started with composite API. If you want to learn about other resources that are mentioned in the response above, let me know and I'll create new blog post(s) for the same.</div><div><h3 style="text-align: left;">Bonus Content: All or None</h3><div>Thank you for reading and reaching to the end of the tutorial (almost). I hope you learned something new! Before we close this post, I want to talk about one most important flag in the composite request - the <b>allOrNone</b> flag. If we mark this flag as true, the whole composite request will rollback if any of the subsequent API request fail. So you can now make sure that you create all the records with correct relationships or none of them.</div><div><br /></div><div>Considering our above example of Account, Contacts and Opportunities creation, let's mark the <b>allOrNone</b> flag as <b>true</b> in the request body as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmnj2JR4K0ObEH4dMswwmfntXBBbTfZfJDZL2DcKGnIlHtdTYExzstCQXjLCXe7tEhBUZq5RlSl6OWpXRb2AQUZSHcurLanoNOxExT5ownpFB-humJkm40Zb28-HR7_nI-zLKWOF425iiEu7580b0TPXFQCkxp5VbxRyRCupJR0oA-44LCZJ50LPYT/s2048/Screenshot%202022-10-16%20at%205.05.44%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1472" data-original-width="2048" height="460" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmnj2JR4K0ObEH4dMswwmfntXBBbTfZfJDZL2DcKGnIlHtdTYExzstCQXjLCXe7tEhBUZq5RlSl6OWpXRb2AQUZSHcurLanoNOxExT5ownpFB-humJkm40Zb28-HR7_nI-zLKWOF425iiEu7580b0TPXFQCkxp5VbxRyRCupJR0oA-44LCZJ50LPYT/w640-h460/Screenshot%202022-10-16%20at%205.05.44%20PM.png" width="640" /></a></div>Notice that I removed the <b>StageName</b> value from the second opportunity record and it's present in the first opportunity record (highlighted above). As StageName is a required field, the opportunity record creation will fail and it should rollback the whole request as we've specified the <b>allOrNone</b> parameter as true. Just to make it more clear, <b>allOrNone</b> parameter is not a part of the compositeRequest array, it's a separate key in the request body as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbiVIGUMvTK4Ktkm8hqtOl5fpN7HEjvnZM2WHyBT9Zb9gZVHujDyUoqzLxL3q-GWOXZz3vQNFHHUa787UhU4WsM9_9-V892XVkgvga8uVHMAvFuGy9XNmawFn6dPCtbEh4RhTU7wkuUOO8_WWyhLqVE1nOR6ZNm_dNs2qt-y2pPf4xIR5Ycq-2F1oj/s2042/Screenshot%202022-10-16%20at%205.08.59%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="526" data-original-width="2042" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbiVIGUMvTK4Ktkm8hqtOl5fpN7HEjvnZM2WHyBT9Zb9gZVHujDyUoqzLxL3q-GWOXZz3vQNFHHUa787UhU4WsM9_9-V892XVkgvga8uVHMAvFuGy9XNmawFn6dPCtbEh4RhTU7wkuUOO8_WWyhLqVE1nOR6ZNm_dNs2qt-y2pPf4xIR5Ycq-2F1oj/w640-h164/Screenshot%202022-10-16%20at%205.08.59%20PM.png" width="640" /></a></div>I am also sharing the whole request body below for your reference:<div><pre>{
"compositeRequest": [
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Account",
"referenceId": "refAccount",
"body": {
"Name": "My Sample Account"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Contact",
"referenceId": "refContact1",
"body": {
"FirstName": "My Sample",
"LastName": "Contact 1",
"AccountId": "@{refAccount.id}"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Contact",
"referenceId": "refContact2",
"body": {
"FirstName": "My Sample",
"LastName": "Contact 2",
"AccountId": "@{refAccount.id}"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Opportunity",
"referenceId": "refOpportunity1",
"body": {
"Name": "My Sample Opportunity 1",
"AccountId": "@{refAccount.id}",
"ContactId": "@{refContact1.id}",
"StageName": "Prospecting",
"CloseDate": "2022-10-20"
}
},
{
"method": "POST",
"url": "/services/data/v55.0/sobjects/Opportunity",
"referenceId": "refOpportunity2",
"body": {
"Name": "My Sample Opportunity 2",
"AccountId": "@{refAccount.id}",
"ContactId": "@{refContact2.id}",
"CloseDate": "2022-10-20"
}
}
],
"allOrNone": true
}</pre><div><div>The response of this request is also provided below:</div><pre>{
"compositeResponse": [
{
"body": [
{
"errorCode": "PROCESSING_HALTED",
"message": "The transaction was rolled back since another operation in the same transaction failed."
}
],
"httpHeaders": {},
"httpStatusCode": 400,
"referenceId": "refAccount"
},
{
"body": [
{
"errorCode": "PROCESSING_HALTED",
"message": "The transaction was rolled back since another operation in the same transaction failed."
}
],
"httpHeaders": {},
"httpStatusCode": 400,
"referenceId": "refContact1"
},
{
"body": [
{
"errorCode": "PROCESSING_HALTED",
"message": "The transaction was rolled back since another operation in the same transaction failed."
}
],
"httpHeaders": {},
"httpStatusCode": 400,
"referenceId": "refContact2"
},
{
"body": [
{
"errorCode": "PROCESSING_HALTED",
"message": "The transaction was rolled back since another operation in the same transaction failed."
}
],
"httpHeaders": {},
"httpStatusCode": 400,
"referenceId": "refOpportunity1"
},
{
"body": [
{
"message": "Required fields are missing: [StageName]",
"errorCode": "REQUIRED_FIELD_MISSING",
"fields": [
"StageName"
]
}
],
"httpHeaders": {},
"httpStatusCode": 400,
"referenceId": "refOpportunity2"
}
]
}</pre><div>As you can see above, the account request, subsequent contact requests, as well as the request to create first opportunity record all were rolled back with errorCode as <b>PROCESSING_HALTED</b> because the 2nd opportunity record creation failed because of a required field that we removed: StageName. I am sharing the postman screenshot below as well for reference:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzbHlXqZ5dAKS7EFZ_lzFnKB8mcANZW7VNSUOuxiyiYjjcNXpDlB2wPR2Yva3cAlvMPbT1J_4IvM9PL-wxTeibAXlsw_dY1F7jJPjsgQhErjhSIKX1ik4X53hSOeOr-ur7Vk0OCKIM3JM2EYnLgyQxvq1cdHSl-VHYnbXLp5UQ1_bbRTIDU9GJztMF/s2038/Screenshot%202022-10-16%20at%205.15.52%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1472" data-original-width="2038" height="462" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzbHlXqZ5dAKS7EFZ_lzFnKB8mcANZW7VNSUOuxiyiYjjcNXpDlB2wPR2Yva3cAlvMPbT1J_4IvM9PL-wxTeibAXlsw_dY1F7jJPjsgQhErjhSIKX1ik4X53hSOeOr-ur7Vk0OCKIM3JM2EYnLgyQxvq1cdHSl-VHYnbXLp5UQ1_bbRTIDU9GJztMF/w640-h462/Screenshot%202022-10-16%20at%205.15.52%20PM.png" width="640" /></a></div>Notice that the status code of the response for main request is <b>200</b> whereas for subsequent requests it's 400. This is because the composite request was still successful even when the subsequent calls failed because there was no error encountered in the composite api callout (as a whole). The correct error code and messages are provided for each subsequent requests in the response body.<div><br /></div><div><div class="separator" style="clear: both;">That's all for this tutorial. I hope you liked it. Let me know your feedback in the comments down below.</div><div class="separator" style="clear: both;"><br /></div><div class="separator" style="clear: both;"><b>Happy Trailblazing!!</b></div></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-37231544864593176092022-10-12T06:19:00.000-07:002022-10-12T06:19:53.548-07:00How to pass data from screen flow to lwc in salesforce?<p>Hello Trailblazers,</p><p><br /></p><p>In this post we're going to learn, how we can pass data from screen flow to lwc component in salesforce. Let's begin by creating our LWC component!</p><h3 style="text-align: left;">LWC: updateRecord</h3><div>We'll create a very simple lwc where we can just pass the <b>recordId</b> and <b>objectName</b> as a parameter and it'll render the whole record for us using <a href="https://developer.salesforce.com/docs/component-library/bundle/lightning-record-form/documentation" target="_blank"><span style="color: #2b00fe;">lightning-record-form</span></a> so that we can easily update record. Let's have a look at the code snippets now:</div><h3 style="text-align: left;">updateRecord.html</h3><pre><template>
<lightning-record-form object-api-name={objectName} record-id={recordId} layout-type="Full" mode="edit">
</lightning-record-form>
</template></pre><div>As you can see above, we're rendering a record form based on the objectName and recordId we've stored in the javascript variables. The layout type is <b>full</b> and the form will open in <b>edit</b> mode by default.</div><h3 style="text-align: left;">updateRecord.js</h3><pre>import { api, LightningElement } from 'lwc';
export default class UpdateRecord extends LightningElement {
@api recordId = "";
@api objectName = "";
}</pre><div>This is even more simpler than html, we just have two variables: <b>recordId</b> and <b>objectName</b> which we'll populate from our flow so that the component can render the record form for us. We have added <b>@api</b> decorator in order to make these variables publically available and accessible from flow.</div><h3 style="text-align: left;">updateRecord.js-meta.xml</h3><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__FlowScreen</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__FlowScreen">
<property name="recordId" type="String" label="Id of the Record"></property>
<property name="objectName" type="String" label="Name of the object"></property>
</targetConfig>
</targetConfigs>
</LightningComponentBundle></pre><div>In the above configuration, we've added the target as: <b>lightning__FlowScreen</b> as we're going to embed this component in a screen flow and under that target, we've mentioned two properties of type String: <b>recordId</b> and <b>objectName</b>. Note that the name of the property is exactly the same as our variable name in JavaScript. As we assign values to these properties from flow, it'll automatically populate our js variables and our component will render the record form. While configuring the flow, we'll see fields with labels assigned to these properties in the component settings.</div><div><br /></div><div>Now, let's create our flow!</div><div><br /></div><h2 style="text-align: left;">Screen Flow: Update Opportunities</h2><div>Let's create a screen flow using which we can update opportunities related to an account (yes you can do it via related list :) however, our main goal here is to learn how to pass information from flow to lwc). I am going to name this flow as <b>Update Opportunities</b> and this is how it looks like:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjr-XT8xZ3GN8XF9LPGwg8RlezjusdPWT_nktOiBXS82wERyMR-tZguP0LQe5A7OA74QwyVnXABDf47p-c2rqtNE-xPLxqwLsncPFV9-uj3ikp0R8GZ5nE5r-j7RUbVPodsUtVlmGgTacMJIlPkSbVBl8a_1yCAzXNje5Y66ywIoJ8RHklVk0uPbBE/s1024/Screenshot%202022-10-11%20at%207.20.37%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1024" data-original-width="1000" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjr-XT8xZ3GN8XF9LPGwg8RlezjusdPWT_nktOiBXS82wERyMR-tZguP0LQe5A7OA74QwyVnXABDf47p-c2rqtNE-xPLxqwLsncPFV9-uj3ikp0R8GZ5nE5r-j7RUbVPodsUtVlmGgTacMJIlPkSbVBl8a_1yCAzXNje5Y66ywIoJ8RHklVk0uPbBE/w391-h400/Screenshot%202022-10-11%20at%207.20.37%20PM.png" width="391" /></a></div><h3 style="text-align: left;">Select Opportunity</h3><div>In our <b>Select Opportunity</b> screen, we've used radio buttons to show different opportunities related to the current account record:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDShO_Y_TQpAT2FtUbAR0svyUX70-p3OQ3QQLmX_cDRbPEMyHWXCoDd0jG6Fs14zSyUyWFmruCf7WdtvJADU2W5nBaOvHUOc223VUD-aBxg_F6SapQOPf1RKh21lc6vZtZVuMInqCB68K7PAOclQoGRdygaPMc_hhh2lnA0xwN6bh3pdB9w1Z_lPdB/s2428/Screenshot%202022-10-11%20at%207.22.55%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1222" data-original-width="2428" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDShO_Y_TQpAT2FtUbAR0svyUX70-p3OQ3QQLmX_cDRbPEMyHWXCoDd0jG6Fs14zSyUyWFmruCf7WdtvJADU2W5nBaOvHUOc223VUD-aBxg_F6SapQOPf1RKh21lc6vZtZVuMInqCB68K7PAOclQoGRdygaPMc_hhh2lnA0xwN6bh3pdB9w1Z_lPdB/w640-h322/Screenshot%202022-10-11%20at%207.22.55%20PM.png" width="640" /></a></div><br /><div>The user will choose one of these options and move to the next screen, where we'll open our updateRecord LWC component with the selected opportunity record. I've used a record choice set to show opportunity records related to the current account as choices, as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnIFKJDawhB4mYPP1o54rs3gKL1nXiR9mhbDVs-_6M94uZf3JT5FrePQl7qXU0p_kWzRkg9kR4JjVB_PmMwvJeUoesamQgH3WBtBKPDS7QGRJ88zFiVGRkrrLUWuI6lcPmMUvI4ApvncX8O5Nl_ZCP1TliKTdgJJNzgw9jr7qBFU2HuSPn3_ZC0h2B/s998/Screenshot%202022-10-11%20at%207.25.23%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="998" data-original-width="650" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnIFKJDawhB4mYPP1o54rs3gKL1nXiR9mhbDVs-_6M94uZf3JT5FrePQl7qXU0p_kWzRkg9kR4JjVB_PmMwvJeUoesamQgH3WBtBKPDS7QGRJ88zFiVGRkrrLUWuI6lcPmMUvI4ApvncX8O5Nl_ZCP1TliKTdgJJNzgw9jr7qBFU2HuSPn3_ZC0h2B/w260-h400/Screenshot%202022-10-11%20at%207.25.23%20PM.png" width="260" /></a></div><br /><div>I am sharing the opportunity record choice set below for reference:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvYfD-WPmPfImstltUVokDsyhn-SZR3xXmAH9TdJfCuzBHPAM1QmqJcnXwyQfAqwyo0UHJlh-Z8sh9YVUiU3HZAABVXHD5IgVbh6zAjGZm2ynaGheFodBV79FZSIS1skLeQlRQfJRTnP2D04h-cz8Qpz3JASilOTN36JzfNjhIoOdcVtG0UY7MVkOB/s1722/Screenshot%202022-10-11%20at%207.27.19%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1420" data-original-width="1722" height="528" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvYfD-WPmPfImstltUVokDsyhn-SZR3xXmAH9TdJfCuzBHPAM1QmqJcnXwyQfAqwyo0UHJlh-Z8sh9YVUiU3HZAABVXHD5IgVbh6zAjGZm2ynaGheFodBV79FZSIS1skLeQlRQfJRTnP2D04h-cz8Qpz3JASilOTN36JzfNjhIoOdcVtG0UY7MVkOB/w640-h528/Screenshot%202022-10-11%20at%207.27.19%20PM.png" width="640" /></a></div><div><br /></div><div>Notice that I've created a new flow variable <b>AccountId</b> using which I am querying my opportunity records related to the current account record. While embedding this flow into the record page, we'll set this <b>AccountId</b> variable with the current account's record id.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNCoD5_Wmj0CubzijWFlLUiZDVopkvAFr41KLlL4BBM84Ke_Qw3hxsbBd1uYloP089IJkX5RnUl7e7b5APfmtGuwelv71gz8XXXCc1BQlnKcLc4GaP8WLNzSDVdP5BNZV_OtFeCTK59nY7y5HSKsheqaBhxqgZxPHJ47p270D8_c96kebjfCjUjX-5/s1718/Screenshot%202022-10-11%20at%207.27.34%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1424" data-original-width="1718" height="530" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNCoD5_Wmj0CubzijWFlLUiZDVopkvAFr41KLlL4BBM84Ke_Qw3hxsbBd1uYloP089IJkX5RnUl7e7b5APfmtGuwelv71gz8XXXCc1BQlnKcLc4GaP8WLNzSDVdP5BNZV_OtFeCTK59nY7y5HSKsheqaBhxqgZxPHJ47p270D8_c96kebjfCjUjX-5/w640-h530/Screenshot%202022-10-11%20at%207.27.34%20PM.png" width="640" /></a></div><br /><div>As you can see above, we've set the <b>Choice Label</b> with Name field of opportunity record and <b>Choice Value</b> as Id field of opportunity record. I am also sharing the <b>AccountId</b> flow variable below for reference:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuYyQG04hn96Kxpb227CaSQAHJFKHcg7bYWIIFNS-hJBEetEuKAgNHucKhrRJnGvsxeYAt1FjNasr1Uk34fHPvBtJNjjbx1Brqp87fs3Lg2X-614oxTtRY75SWJN4JuxdcWYtGh37TggnILnIbN0XnjrZTvaj7U5QzjPaV0xzHzE8MTtEGc5gG28Z7/s1722/Screenshot%202022-10-11%20at%207.31.03%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="756" data-original-width="1722" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuYyQG04hn96Kxpb227CaSQAHJFKHcg7bYWIIFNS-hJBEetEuKAgNHucKhrRJnGvsxeYAt1FjNasr1Uk34fHPvBtJNjjbx1Brqp87fs3Lg2X-614oxTtRY75SWJN4JuxdcWYtGh37TggnILnIbN0XnjrZTvaj7U5QzjPaV0xzHzE8MTtEGc5gG28Z7/w640-h280/Screenshot%202022-10-11%20at%207.31.03%20PM.png" width="640" /></a></div><br /><div>It's Available for input as we're going to populate this variable from our lightning record page for account. Now, let's move on to the <b>Update Opportunity</b> screen.</div><div><br /></div><h2 style="text-align: left;">Update Opportunity</h2><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrj6yWBpLXbF8vgmCSAk7udA3IsEpDomQNfYD-40sfialmjlt4sgZn4QYFtUthltZKqtJD0zRCx-SpnucjLiWr-pS_F4g2kawsJGn6w5sztlGeHWkvQbHT3yYz_WaHKGTAYALZr3IdzoDi2i3a7IY94m50R7VHFx6PmVbfmoVZA2sL6FtvMJBApsz0/s2446/Screenshot%202022-10-11%20at%207.33.41%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1242" data-original-width="2446" height="324" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrj6yWBpLXbF8vgmCSAk7udA3IsEpDomQNfYD-40sfialmjlt4sgZn4QYFtUthltZKqtJD0zRCx-SpnucjLiWr-pS_F4g2kawsJGn6w5sztlGeHWkvQbHT3yYz_WaHKGTAYALZr3IdzoDi2i3a7IY94m50R7VHFx6PmVbfmoVZA2sL6FtvMJBApsz0/w640-h324/Screenshot%202022-10-11%20at%207.33.41%20PM.png" width="640" /></a></div><br /><div>Notice the highlighted sections above, on the left, you can see our <b>updateRecord</b> lwc component which is available under <b>Custom</b> section. I've embedded the same component in the screen which you can see in the middle and on the right, we've set the properties of our lwc component. Notice that the labels we provided to those properties are visible here: <b>Id of the Record</b> and <b>Name of the object</b>. In the id, I've passed the Opportunity Record Set variable which will automatically share the Id value of our selected record and in the name I've passed the object name as <b>Opportunity</b>.</div><div><br /></div><div>As we open the <b>Advanced</b> section on the right side, we need to select the <b>Refresh inputs to incorporate changes elsewhere in the flow</b> option as given below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQPkwf4ukvv9_RJq96P3Um8eAw15Q40_diEpEfzlwv0fioWjxaVrvR2VKwcQ_eM9ZtesVMkoGiZJsX4CK1i8A0RpD6lvnHLtq8mSVI-WM2TfZ3oKwGJJTtuCoYfai4mStsWIdi-3xo9FdFY4BRszB3XqLR9xar02vhLdWBuqCa-mzmGXlyvE9rkfPL/s1158/Screenshot%202022-10-11%20at%207.42.32%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1158" data-original-width="694" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQPkwf4ukvv9_RJq96P3Um8eAw15Q40_diEpEfzlwv0fioWjxaVrvR2VKwcQ_eM9ZtesVMkoGiZJsX4CK1i8A0RpD6lvnHLtq8mSVI-WM2TfZ3oKwGJJTtuCoYfai4mStsWIdi-3xo9FdFY4BRszB3XqLR9xar02vhLdWBuqCa-mzmGXlyvE9rkfPL/w240-h400/Screenshot%202022-10-11%20at%207.42.32%20PM.png" width="240" /></a></div><div><br /></div>This is important because: Let's say the user chose the first opportunity record out of the radio buttons on the 1st screen and moved ahead to the 2nd screen. Now, user decides to go back and choose the second opportunity record instead of the first one. So, in this case, the inputs provided to our component should refresh automatically so that the lwc component displays the updated form with second opportunity record based on the updated record id passed to the component.<div><br /></div><div>Make sure to activate the flow before we proceed ahead.</div><div><br /><h2 style="text-align: left;">Add Screen Flow to Record Page</h2><div>Now, we're almost done with our task, let's embed our flow to the Account's Lightning Record Page and see it in action!</div><div><br /></div><div>1. Go to any account record and click on Edit Page from the gear menu as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbWzDXUujuA3i38K-vCQy9qNfiIKHpiDf94CdpBRF5hbLchRtLIXeV6-k6W5h5suDgXvT7yogrU7J2bmyzYD3TQwP2ilbK6gNvxeY9AalUKHQ8oCU8GwRtmNPg4Hi7isCYPvbHW2hmI__ryUQdkN8Ufk-C3_vIs1XwtKKBdPJlKrvWHXONCs-f-ETU/s2880/Screenshot%202022-10-11%20at%207.47.58%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="672" data-original-width="2880" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbWzDXUujuA3i38K-vCQy9qNfiIKHpiDf94CdpBRF5hbLchRtLIXeV6-k6W5h5suDgXvT7yogrU7J2bmyzYD3TQwP2ilbK6gNvxeY9AalUKHQ8oCU8GwRtmNPg4Hi7isCYPvbHW2hmI__ryUQdkN8Ufk-C3_vIs1XwtKKBdPJlKrvWHXONCs-f-ETU/w640-h150/Screenshot%202022-10-11%20at%207.47.58%20PM.png" width="640" /></a></div><div><br /></div><div>2. On the page builder, we'll search for flow component from the left search bar and embed it into our lightning page as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpyT-clLJiJu0A5GLmnUL5dLLdHqYtqVEqifj528no6rNyA45u4qH-aFQSE98y4DXPBt0_FmggiTaGAfJKt8vMh2k5yp2oIPve3eRaDWNUxGQllpF9DXGtc6hMFVfLJ6PgesM4lPdnXNWcGH1IO1iaC1Sj48bcJ1s9eU4SKNC-ErzC2XihYV6INV6e/s2880/Screenshot%202022-10-11%20at%207.50.10%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1220" data-original-width="2880" height="272" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpyT-clLJiJu0A5GLmnUL5dLLdHqYtqVEqifj528no6rNyA45u4qH-aFQSE98y4DXPBt0_FmggiTaGAfJKt8vMh2k5yp2oIPve3eRaDWNUxGQllpF9DXGtc6hMFVfLJ6PgesM4lPdnXNWcGH1IO1iaC1Sj48bcJ1s9eU4SKNC-ErzC2XihYV6INV6e/w640-h272/Screenshot%202022-10-11%20at%207.50.10%20PM.png" width="640" /></a></div><br /><div>As you click on the embedded flow component, you'll have configurations on the right hand side where you can select the flow <b>Update Opportunities</b>. Note that I've selected <b>Pass record ID into this variable</b> checkbox for the <b>AccountId</b> variable. This will automatically pass the current account's record id to the flow.</div><div><br /></div><div>Click on <b>Save</b> button and <b>Activate</b> the page (if required) as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcvRztsT8EIOBUXx2KHHJsakyARPxU43XDe2XQlYF860t31-hx8uVVSas7EFQ-3yeaaGLmoDPrN2hvueKMy2rfk4Kv2Mph7r7OxiCrCWp1m9--XMLCs6zTBgQl6IQDdF76v49FnfI4GR8-gw44f32rgrHSA4e22V9FTou_PgDhIsqXDiEaYOgayINt/s1740/Screenshot%202022-10-11%20at%208.21.40%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="970" data-original-width="1740" height="356" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcvRztsT8EIOBUXx2KHHJsakyARPxU43XDe2XQlYF860t31-hx8uVVSas7EFQ-3yeaaGLmoDPrN2hvueKMy2rfk4Kv2Mph7r7OxiCrCWp1m9--XMLCs6zTBgQl6IQDdF76v49FnfI4GR8-gw44f32rgrHSA4e22V9FTou_PgDhIsqXDiEaYOgayINt/w640-h356/Screenshot%202022-10-11%20at%208.21.40%20PM.png" width="640" /></a></div><div><br /></div>Click on <b>Activate</b> button. Assign the current page as org default for now:</div><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3zKPyuSnF-NFOeaNWeS6k-7h4vBQ7MCYGT-QKwhvdk_LBQCuuIlQyF9AOlQacjWI8BQhEdrnhEDywp0fTmO1BHUkhXNnWZ-QLpDzzJ5LGtgJwx45hbp7HpPpmwkBDvX2m-ubk9trjmtLO9NzySiMd9ocd-XZFSVPdtFt9ZgeEnBawIHJZZNjvTuIB/s1984/Screenshot%202022-10-11%20at%208.22.00%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1352" data-original-width="1984" height="436" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3zKPyuSnF-NFOeaNWeS6k-7h4vBQ7MCYGT-QKwhvdk_LBQCuuIlQyF9AOlQacjWI8BQhEdrnhEDywp0fTmO1BHUkhXNnWZ-QLpDzzJ5LGtgJwx45hbp7HpPpmwkBDvX2m-ubk9trjmtLO9NzySiMd9ocd-XZFSVPdtFt9ZgeEnBawIHJZZNjvTuIB/w640-h436/Screenshot%202022-10-11%20at%208.22.00%20PM.png" width="640" /></a></div><div><br /></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirjaUK-Knk-EuXvqJDwuYINNN7LpR6aV0sMF-iwbAzeR672jd-1FbmqVIotAjwNCRmBX8hOZWATq-Kj1MCECXnTl3qBYK_HYTvzsQx9EzXjxRUDdrZpuI6mrXoS2AlAkTVNnKX0NRjL6Bys8bIEc1FVNjlReHxXiHVfeDDgFOnlY90uBr_0_j_EuQI/s2144/Screenshot%202022-10-11%20at%208.22.24%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1066" data-original-width="2144" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirjaUK-Knk-EuXvqJDwuYINNN7LpR6aV0sMF-iwbAzeR672jd-1FbmqVIotAjwNCRmBX8hOZWATq-Kj1MCECXnTl3qBYK_HYTvzsQx9EzXjxRUDdrZpuI6mrXoS2AlAkTVNnKX0NRjL6Bys8bIEc1FVNjlReHxXiHVfeDDgFOnlY90uBr_0_j_EuQI/w640-h318/Screenshot%202022-10-11%20at%208.22.24%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Click <b>Next</b> button</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi16nHKR7xduWyUPGpgfXIaXgb73GH6i2P69i3HbGpCzkyPuw_KDaOuJjMpPwTRQzv1ajTrEvLugMcLPx6sXHKat9bKWsft8M4CXHa9uc7OsRvJ2DVJTAhU2RiGz0rDnpx2b74q6pti75zU1tccwGspj8uwKC2DdGwstSYyz918M6nXIasUTSgnEitF/s2026/Screenshot%202022-10-11%20at%208.22.29%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="854" data-original-width="2026" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi16nHKR7xduWyUPGpgfXIaXgb73GH6i2P69i3HbGpCzkyPuw_KDaOuJjMpPwTRQzv1ajTrEvLugMcLPx6sXHKat9bKWsft8M4CXHa9uc7OsRvJ2DVJTAhU2RiGz0rDnpx2b74q6pti75zU1tccwGspj8uwKC2DdGwstSYyz918M6nXIasUTSgnEitF/w640-h270/Screenshot%202022-10-11%20at%208.22.29%20PM.png" width="640" /></a></div><div><br /></div>Click on <b>Save</b> button and that's all!<div><br /><div><h2 style="text-align: left;">Demo</h2><div>This is how our flow looks like on an account record's detail page:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigrp2pNr8mtDtPkDwujZVMoZcE3VZL3ZeVma7ZasxtlH78PZpCDpsUNgXPg9D4rzPzrIsbd5FDP43A5TJDfk1Nv02IUwnwPWT15zWeYsevvKxUsbRRTCGBKTXlctT-RCBE_J6XgYG0AzSdC9IrGSbBjtk-6KE3aUPW3dVbsEyG1Vapib8gvZoc-7Kw/s2880/Screenshot%202022-10-11%20at%208.23.46%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1206" data-original-width="2880" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigrp2pNr8mtDtPkDwujZVMoZcE3VZL3ZeVma7ZasxtlH78PZpCDpsUNgXPg9D4rzPzrIsbd5FDP43A5TJDfk1Nv02IUwnwPWT15zWeYsevvKxUsbRRTCGBKTXlctT-RCBE_J6XgYG0AzSdC9IrGSbBjtk-6KE3aUPW3dVbsEyG1Vapib8gvZoc-7Kw/w640-h268/Screenshot%202022-10-11%20at%208.23.46%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">As you select an opportunity and click <b>Next</b> you'll see our lwc component with the selected opportunity record in edit mode as shown below:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7M0NNkSVNj-2aqFNcmeWeFM29Smpd1igyDQisp9KVP6Dv9iLG5yIB_-YSUlKv_CyC0B2urgbb8D2DaHO8qcDQQH-w4mrq7SJSozTv-AiOkoh9vUgKzgw_gh_hWd-NZY9wiCKtrPXxv6H6YnVTycNO_wdDjFpfy7siFWcdeAB3B0Ix225Y23DT1hDh/s2605/screencapture-app-dream-3939-dev-ed-lightning-force-lightning-r-Account-0016D00000fGV02QAG-view-2022-10-12-18_13_37%20(1).png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2605" data-original-width="919" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7M0NNkSVNj-2aqFNcmeWeFM29Smpd1igyDQisp9KVP6Dv9iLG5yIB_-YSUlKv_CyC0B2urgbb8D2DaHO8qcDQQH-w4mrq7SJSozTv-AiOkoh9vUgKzgw_gh_hWd-NZY9wiCKtrPXxv6H6YnVTycNO_wdDjFpfy7siFWcdeAB3B0Ix225Y23DT1hDh/w226-h640/screencapture-app-dream-3939-dev-ed-lightning-force-lightning-r-Account-0016D00000fGV02QAG-view-2022-10-12-18_13_37%20(1).png" width="226" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div>We have the <b>Save</b> and <b>Cancel</b> button coming from the record form using which you can save your changes. You can click on the <b>Finish</b> button once you're done and it'll restart the flow. </div><div><br /></div><div><b>Note: </b>After selecting an opportunity from the first screen and clicking Next, you can also click Previous button and select a different opportunity to verify, the form should update on the second screen automatically with the new opportunity record.</div><div><br /></div><div><div class="separator" style="clear: both;">That's all for this tutorial. I hope you liked it. Let me know your feedback in the comments down below.</div><div class="separator" style="clear: both;"><br /></div><div class="separator" style="clear: both;"><b>Happy Trailblazing!!</b></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-57386249445613711992022-05-13T21:30:00.001-07:002022-05-13T21:30:00.192-07:00How to setup prettier for apex in VSCode?<p>Hello Trailblazers,</p><p><br /></p><p>In this post, we're going to learn how to setup prettier for apex in VSCode. We're going to use a plugin named as <a href="https://www.npmjs.com/package/prettier-plugin-apex" rel="nofollow" target="_blank"><span style="color: #2b00fe;">prettier-plugin-apex</span></a>. Once you've <a href="https://youtu.be/pRkcutdeMfY" target="_blank"><span style="color: #2b00fe;">created a salesforce project</span></a>, the first step is to install <a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" rel="nofollow" target="_blank"><span style="color: #2b00fe;">Prettier - Code formatter</span></a> extension.</p><p>Click on the <b>Extensions Tab</b> in the left sidebar in your VSCode and search for prettier as shown below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhotoQYTxZz-gnOpahnRMLSEDG50TeTcvZLBRiAISaEABIzEkT8oCepXvkijNAAvbzvJKcYIUupFitWTS9BlGgxsSgIIZnUFzruH3QZI-GuJt6cVjBBUp0Zj59vFFZnSuZm1APrKoPlb28s3THEzGEv39LISzwBPF_AwlS85wIm_DvFbmoitWPo5EKw/s2116/Screenshot%202022-05-11%20at%209.16.10%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="640" data-original-width="2116" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhotoQYTxZz-gnOpahnRMLSEDG50TeTcvZLBRiAISaEABIzEkT8oCepXvkijNAAvbzvJKcYIUupFitWTS9BlGgxsSgIIZnUFzruH3QZI-GuJt6cVjBBUp0Zj59vFFZnSuZm1APrKoPlb28s3THEzGEv39LISzwBPF_AwlS85wIm_DvFbmoitWPo5EKw/w640-h194/Screenshot%202022-05-11%20at%209.16.10%20AM.png" width="640" /></a></div><p>The first option that you'll see is for <b>Prettier - Code Formatter</b> open that and click on the <b>Install</b> button.</p><p>Once you have installed prettier, now it's time to install <b>prettier-plugin-apex</b>. You can install the plugin <b>in your project only </b>by running the command given below:</p><pre>npm install --save-dev --save-exact prettier prettier-plugin-apex</pre><p>The output of the above command is given below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVLKfg37WECKJxt2EkeU8-gD6WP0EiiZmpWFo57qIaZ-r9thQf1c4f61j__Hp1OKx6oHHl6waLlqlpUPMBBfWp7x98EsIwuRWSmJ87PjFzMEb_5GZvEEJ5QobFPqNMfkULT99sXLQMP8vJgSHk18-v7E9_ErN3B_iO9CmnneWZp-OspqCOD0iQr9_K/s1692/Screenshot%202022-05-11%20at%209.20.18%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="342" data-original-width="1692" height="130" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVLKfg37WECKJxt2EkeU8-gD6WP0EiiZmpWFo57qIaZ-r9thQf1c4f61j__Hp1OKx6oHHl6waLlqlpUPMBBfWp7x98EsIwuRWSmJ87PjFzMEb_5GZvEEJ5QobFPqNMfkULT99sXLQMP8vJgSHk18-v7E9_ErN3B_iO9CmnneWZp-OspqCOD0iQr9_K/w640-h130/Screenshot%202022-05-11%20at%209.20.18%20AM.png" width="640" /></a></div><p>Please note that this will install <b>prettier-plugin-apex</b> locally in your project. If you want to install it globally so that you can use it in all the projects, you can use the below command:</p><pre>npm install --global prettier prettier-plugin-apex</pre><p>If you've installed it locally, it'll the <b>package.json</b> file in your salesforce project and you'll find the entry for <b>prettier</b> under <b>scripts</b> as shown below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg95wBjp1Vy8vPxl01-eENWKS7SR9stpgYHGXRgh11hHYyE55zkEzmVZeOJuU5oV8_y3m4P28igcmgtF2KgmTUcfb9V2yA7cmfyjGRzHeq9a0oaxnJpE1VYt-JRIiaCvrQNd-NgGTco4FSuB2d__B1J0onHL5wyxYta42x4a5biZMNgxlLcVyWdX5K0/s1878/Screenshot%202022-05-11%20at%209.42.17%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="444" data-original-width="1878" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg95wBjp1Vy8vPxl01-eENWKS7SR9stpgYHGXRgh11hHYyE55zkEzmVZeOJuU5oV8_y3m4P28igcmgtF2KgmTUcfb9V2yA7cmfyjGRzHeq9a0oaxnJpE1VYt-JRIiaCvrQNd-NgGTco4FSuB2d__B1J0onHL5wyxYta42x4a5biZMNgxlLcVyWdX5K0/w640-h152/Screenshot%202022-05-11%20at%209.42.17%20AM.png" width="640" /></a></div>So, if you want to run prettier for your project, you can run it by executing the below command:<div><pre>npm run prettier</pre><p>I tried this command in my project, so you can see the sample output below:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4mY9D5l-SQ-AUiUDhEbmeSCE1dOhBTpk2595hyfK61p8DMvWUeDusdtLEuPPtBFYU8IKr7E_WKMl5SI1GsK9WLwZE6jCWTwQs7G4g9DDBteGAGgVJibs86n5lHJC3bDpU5mHCJ8GfBaB3qF7IbPz9uVK1WureTJ876WR7qu_2fiRByXUR37xWg90k/s1364/Screenshot%202022-05-11%20at%209.51.44%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="962" data-original-width="1364" height="452" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4mY9D5l-SQ-AUiUDhEbmeSCE1dOhBTpk2595hyfK61p8DMvWUeDusdtLEuPPtBFYU8IKr7E_WKMl5SI1GsK9WLwZE6jCWTwQs7G4g9DDBteGAGgVJibs86n5lHJC3bDpU5mHCJ8GfBaB3qF7IbPz9uVK1WureTJ876WR7qu_2fiRByXUR37xWg90k/w640-h452/Screenshot%202022-05-11%20at%209.51.44%20AM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">You'll see that the apex files are also formatted. Let's have a look at the test class formatted using prettier, that we created for salesforce flow in the <a href="https://www.sfdcstop.com/2022/05/how-to-create-test-class-for-flow-test.html" target="_blank"><span style="color: #2b00fe;">previous post</span></a>:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi3tSpqYgvcqNMKIgge3cvaZiKxrEkfF6RaKwXD_h66wxuxlHauKW67cz63eCLdQlz9K4YThjj4aR30uaND0all8WrVnpLV9tI8LJq7XwNPWb8APdWmjXWvGnXiYZvwUOrT9VzDIl_F-xX8CLvtWWgpgf-QXVhhwJWCicKJlOaXTBMCXVHORQLxrGK/s1572/Screenshot%202022-05-11%20at%209.54.04%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1542" data-original-width="1572" height="628" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi3tSpqYgvcqNMKIgge3cvaZiKxrEkfF6RaKwXD_h66wxuxlHauKW67cz63eCLdQlz9K4YThjj4aR30uaND0all8WrVnpLV9tI8LJq7XwNPWb8APdWmjXWvGnXiYZvwUOrT9VzDIl_F-xX8CLvtWWgpgf-QXVhhwJWCicKJlOaXTBMCXVHORQLxrGK/w640-h628/Screenshot%202022-05-11%20at%209.54.04%20AM.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: left;">As you've formatted the whole project once, a better option is to setup prettier so that it formats the file automatically as you save it. You can do that by following the below steps:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">1. Open the command pallette (Ctrl + Shift + P for Windows, Command + Shift + P for Mac) and search for User settings.</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlBzxryoa_wiAxMXeMV7kCZ0-THUQtWDX37cXkDA02V3QvwyLPv-Q26SP-ecYtJlYVUHKs0alkDgIWEXWz1cWLktPGNRlWGUSouDc3u2O8AdP6rch-w7IfhZEShfYqcCfViSSzSLZi3o7r3ERGs553nwNpjMbllsdHQyK_cKKJ6pPK4Eju_EdhfbgY/s1298/Screenshot%202022-05-11%20at%209.58.02%20AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="160" data-original-width="1298" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlBzxryoa_wiAxMXeMV7kCZ0-THUQtWDX37cXkDA02V3QvwyLPv-Q26SP-ecYtJlYVUHKs0alkDgIWEXWz1cWLktPGNRlWGUSouDc3u2O8AdP6rch-w7IfhZEShfYqcCfViSSzSLZi3o7r3ERGs553nwNpjMbllsdHQyK_cKKJ6pPK4Eju_EdhfbgY/w640-h78/Screenshot%202022-05-11%20at%209.58.02%20AM.png" width="640" /></a></div>2. Click on <b>Preferences:Open User Settings</b> as shown above and search for <b>format on save</b></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJLY9pIQ6T97g_pE-E3Ahd-LHkgtwBI-bsAmI0pgwWouv2GVSx0OJyShGidGxYoKWX6wdV2cNB1VQzDtEJAnrh_GDaxmD8uLdSVw8k7uEE-VondzCZ5pOjPklsEaRzGMRgPNP2EtM2YOk8PyFuiVVfrI4feLBXfjCZBtaPJpZGgb_dYOjDNN9xwazC/s2052/Screenshot%202022-05-11%20at%208.41.06%20PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="692" data-original-width="2052" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJLY9pIQ6T97g_pE-E3Ahd-LHkgtwBI-bsAmI0pgwWouv2GVSx0OJyShGidGxYoKWX6wdV2cNB1VQzDtEJAnrh_GDaxmD8uLdSVw8k7uEE-VondzCZ5pOjPklsEaRzGMRgPNP2EtM2YOk8PyFuiVVfrI4feLBXfjCZBtaPJpZGgb_dYOjDNN9xwazC/w640-h216/Screenshot%202022-05-11%20at%208.41.06%20PM.png" width="640" /></a></div>Click the <b>Editor: Format On Save</b> checkbox under the <b>User</b> tab as shown above. Also click the same checkbox under the <b>Workspace</b> tab as shown in the below screenshot:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6sq9PbsSWaylbfSo5J0PPNAvQn2lCcFLBdcO3YKBs0BwE3X6mFir3CuYfJAlbslVOldckbOZMNFlTgiYNhynnXmAng3yTm8e61rIasYVHd4G4KXDEiKPHiUODtD3CSPtaQvCC70DYkvUzQOJh8RUxhXS84DVr5v1_qx9_QxywO_b7RoO1B8eZZq1r/s2098/Screenshot%202022-05-11%20at%208.41.31%20PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="752" data-original-width="2098" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6sq9PbsSWaylbfSo5J0PPNAvQn2lCcFLBdcO3YKBs0BwE3X6mFir3CuYfJAlbslVOldckbOZMNFlTgiYNhynnXmAng3yTm8e61rIasYVHd4G4KXDEiKPHiUODtD3CSPtaQvCC70DYkvUzQOJh8RUxhXS84DVr5v1_qx9_QxywO_b7RoO1B8eZZq1r/w640-h230/Screenshot%202022-05-11%20at%208.41.31%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">Now your apex file will format automatically using prettier as you save it. <b>Congratulations!! You've successfully setup prettier for apex in VSCode and it's ready for use</b>.<br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">That's all for this tutorial. I hope you liked it. Let me know your feedback in the comments down below.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><b>Happy Trailblazing!!</b></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com3tag:blogger.com,1999:blog-8643239839712475364.post-17089405484286925672022-05-07T03:19:00.002-07:002022-05-07T03:25:29.695-07:00How to create test class for a flow? | Test coverage for flows | Salesforce Flow Test Class Basics<p>Hello Trailblazers,</p><p><br /></p><p>In this post we're going to learn <b>How to create test class for a flow?</b> Most of the developers I've met have never written a test class for flow and some of them are not even aware that we can write a test class for flow and evaluate the test coverage. The question that I get is: <b>Is it possible to create test class for a flow??</b></p><p><b><br /></b></p><p><b>YES, THIS IS POSSIBLE!! LET'S SEE HOW?</b></p><h3 style="text-align: left;">Create a Flow</h3><p>First of all, let's create a simple flow. I am sharing the details of the flow below:</p><p><br /></p><p><b>Flow Type</b>: Record Triggered Flow</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGvJQiN_9yaMm_Km5sP-Yg9oZaU4nZj20jldx8YcRiicAHRQx6-ibVjNvjq3zBTUWAzBusbaZ19O1u1kCRjMPN64wwWUE39rvTk-AGLzK5XZ_J5-MwyGEvvq8nxMu-pPY0AgBb7La8EenOLAAw9W5VBR5p7RkYNH_VWfrvtPXEXorGciQYMAQ2eBiC/s2880/Screenshot%202022-05-07%20at%207.13.15%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1452" data-original-width="2880" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGvJQiN_9yaMm_Km5sP-Yg9oZaU4nZj20jldx8YcRiicAHRQx6-ibVjNvjq3zBTUWAzBusbaZ19O1u1kCRjMPN64wwWUE39rvTk-AGLzK5XZ_J5-MwyGEvvq8nxMu-pPY0AgBb7La8EenOLAAw9W5VBR5p7RkYNH_VWfrvtPXEXorGciQYMAQ2eBiC/w640-h322/Screenshot%202022-05-07%20at%207.13.15%20AM.png" width="640" /></a></div><p><b>Object</b>: Lead</p><p><b>Trigger the Flow When</b>: A record is created or updated</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY5oEdapTgaq8JTAiU4mm1g-AoUMIM-Yz8hLMwyGSbJHq0gWQ7ykPDgeU6Irffmgxe7aW1FWIP9XZPTaE7ECZDJXhl3lFEIsNhpyiAO4s6bCKJWdUF7EFrxsCARiC75za8_hvAspAaW2pJjjbmOajDNdsldNJ1dySm-d3WpcwRwOI5JhrZOKfzWVnX/s2438/Screenshot%202022-05-07%20at%207.19.51%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="784" data-original-width="2438" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY5oEdapTgaq8JTAiU4mm1g-AoUMIM-Yz8hLMwyGSbJHq0gWQ7ykPDgeU6Irffmgxe7aW1FWIP9XZPTaE7ECZDJXhl3lFEIsNhpyiAO4s6bCKJWdUF7EFrxsCARiC75za8_hvAspAaW2pJjjbmOajDNdsldNJ1dySm-d3WpcwRwOI5JhrZOKfzWVnX/w640-h206/Screenshot%202022-05-07%20at%207.19.51%20AM.png" width="640" /></a></div><p></p><p><b>Entry Conditions</b>: All Conditions Are Met (AND) -> AnnualRevenue -> Is Changed -> True</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsRzixfwDCidpVzFXeRch3u0XSW9JeThAfKDpiVT-Zv1_5RSQC5KX0tVWZm7JPVXKp19crKLLo2pA-9hmYu_cBA416gGZDMK2PkIisJatYyZ70ab9G7t_5jiyBts3fHA3r-xszbPqrruauJwmwJ1Li9JW_0eEpAF7phnbEu64hHcfREVlLfqhvVAkR/s2424/Screenshot%202022-05-07%20at%207.20.41%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="680" data-original-width="2424" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsRzixfwDCidpVzFXeRch3u0XSW9JeThAfKDpiVT-Zv1_5RSQC5KX0tVWZm7JPVXKp19crKLLo2pA-9hmYu_cBA416gGZDMK2PkIisJatYyZ70ab9G7t_5jiyBts3fHA3r-xszbPqrruauJwmwJ1Li9JW_0eEpAF7phnbEu64hHcfREVlLfqhvVAkR/w640-h180/Screenshot%202022-05-07%20at%207.20.41%20AM.png" width="640" /></a></div><p><b>Optimize the Flow for</b>: Fast Field Updates</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_bZmdryRb4BaNzkiTdqAk2MCDIsBFp-EcELIbBihPVBzwPCSPufcubI3qJXqU6kb3Ji1Cy39muVBm7n8ap-juLlr0Dnev255mpQO3Td8eoZY5K9nh5wF3XxiGHffi7S4m5BDpztCEid2k80iLQNbgo8euYRwJJrw0aZeaGOthv2_U4f-ItSWH_Y74/s2420/Screenshot%202022-05-07%20at%207.20.55%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="900" data-original-width="2420" height="238" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_bZmdryRb4BaNzkiTdqAk2MCDIsBFp-EcELIbBihPVBzwPCSPufcubI3qJXqU6kb3Ji1Cy39muVBm7n8ap-juLlr0Dnev255mpQO3Td8eoZY5K9nh5wF3XxiGHffi7S4m5BDpztCEid2k80iLQNbgo8euYRwJJrw0aZeaGOthv2_U4f-ItSWH_Y74/w640-h238/Screenshot%202022-05-07%20at%207.20.55%20AM.png" width="640" /></a></div><p><b>Flow Label</b>: Update Lead Rating based on Annual Revenue</p><p><b>Flow Description:</b> This flow will update lead rating based on the value of annual revenue</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtMrATSeAwvf7gUFOkXA8yX-t076ME2gbTgxPHYDsCow_qogW8CpcW5Ba6P_4W1-oPjFD1IpsekmrJEwn8icFbHJFCyQwLLXX25RberavM0jz9CFnWYbPl-QD645KzOioz0X87exnGRe4uyvKQ8Uy7u6avS06XTxsvZ8Yf2pAhCIIAfa3QeLpEShLS/s1748/Screenshot%202022-05-07%20at%207.23.03%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="718" data-original-width="1748" height="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtMrATSeAwvf7gUFOkXA8yX-t076ME2gbTgxPHYDsCow_qogW8CpcW5Ba6P_4W1-oPjFD1IpsekmrJEwn8icFbHJFCyQwLLXX25RberavM0jz9CFnWYbPl-QD645KzOioz0X87exnGRe4uyvKQ8Uy7u6avS06XTxsvZ8Yf2pAhCIIAfa3QeLpEShLS/w640-h262/Screenshot%202022-05-07%20at%207.23.03%20AM.png" width="640" /></a></div><p><br /></p><p><b>What this flow is going to do?</b></p><p><b><br /></b></p><p>Our <b>flow will update the Lead Rating based on the value of Annual Revenue</b> of lead. There are 3 conditions that it'll cover:</p><p><br /></p><p></p><ol style="text-align: left;"><li>Annual Revenue is less than 25,000 -> Lead Rating = Cold<br /><br /></li><li>Annual Revenue is between 25,000 and 75,000 -> Lead Rating = Warm<br /><br /></li><li>Annual Revenue is greater than 75,000 -> Lead Rating = Hot</li></ol><div><br /></div><div>So, we created a decision element named "Check Annual Revenue". It'll have 3 outcomes based on the above 3 conditions:</div><div><br /></div><div><b>Less than 25,000</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_vT191lv4qYE2WheTIdqSBhCx_ovg5aw4DOrec-lPse2DF6LQXRhr1Fd86qicseo2yiAo30MTTHJ4I_ZQ7VmljT6wuuahcKuXqreT3xDrckqkqIGq2NQfnWipJ-aWk4y_eGXGwFawosByp48xVEd10iS9sX5AEKapGDFPN4k187JO11OdS-gSrT2i/s2432/Screenshot%202022-05-07%20at%207.38.00%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1240" data-original-width="2432" height="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_vT191lv4qYE2WheTIdqSBhCx_ovg5aw4DOrec-lPse2DF6LQXRhr1Fd86qicseo2yiAo30MTTHJ4I_ZQ7VmljT6wuuahcKuXqreT3xDrckqkqIGq2NQfnWipJ-aWk4y_eGXGwFawosByp48xVEd10iS9sX5AEKapGDFPN4k187JO11OdS-gSrT2i/w640-h326/Screenshot%202022-05-07%20at%207.38.00%20AM.png" width="640" /></a></div><br /><b>Between 25,000 and 75,000</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyM5hNxMTdHW1F0ZHLwOUMeJFqr_WGWKngXAthaLF1NQ7Yxk41509nuNmeEgDsragOvAmnmEXJ8myVlyRK3U-xH1VxbzATsTVWZptnAJM_OP-UGe8pAsYgHZFJOYB4LjtZv2zHLt4052VTNFeTM19fEAqeyS0I77lPbF6xH0LPLj1md0PIkM8jlgul/s2432/Screenshot%202022-05-07%20at%207.40.09%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1354" data-original-width="2432" height="356" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyM5hNxMTdHW1F0ZHLwOUMeJFqr_WGWKngXAthaLF1NQ7Yxk41509nuNmeEgDsragOvAmnmEXJ8myVlyRK3U-xH1VxbzATsTVWZptnAJM_OP-UGe8pAsYgHZFJOYB4LjtZv2zHLt4052VTNFeTM19fEAqeyS0I77lPbF6xH0LPLj1md0PIkM8jlgul/w640-h356/Screenshot%202022-05-07%20at%207.40.09%20AM.png" width="640" /></a></div><br /></div><div><b>Greater than 75,000 (Default Outcome)</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtoFCUUMTR_Cds-SIMOKTlFquzyk66CSGVvMZhojlUrcdfBe0pN_-WwfYM51Z9RK6gr4BE1naEhnAacKb4ffs5MOioQApBA9LiU4hNFJkHouZb75El6nTL1yGfn_h_VIZrXi-VywluLaf8t4T41oLlwSrC2SczeGFrpQLdAvJ1AnstIM6tMXCzG2gZ/s2418/Screenshot%202022-05-07%20at%207.40.28%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1142" data-original-width="2418" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtoFCUUMTR_Cds-SIMOKTlFquzyk66CSGVvMZhojlUrcdfBe0pN_-WwfYM51Z9RK6gr4BE1naEhnAacKb4ffs5MOioQApBA9LiU4hNFJkHouZb75El6nTL1yGfn_h_VIZrXi-VywluLaf8t4T41oLlwSrC2SczeGFrpQLdAvJ1AnstIM6tMXCzG2gZ/w640-h302/Screenshot%202022-05-07%20at%207.40.28%20AM.png" width="640" /></a></div><br /></div><div>Now, let's create 3 update elements as well:</div><div><br /></div><div><b>Update Rating to Cold</b></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbncrpZoVCYcMx_SsEer8SOEp1zbx4bH1LrbgFO7D_lTTFpEpEXXoD2FsOxbs95yx9HO8B4xJqJfCXQjZhh3NE4SeUF-nQurCeofur9Js5OyNDgH947A27nwJk7GvV055Qpuir1ufigGD8Ao-_HBtgOY9CVFjMDfk1zSI1i0Bs_lvmWrE6w4LUZLYv/s1710/Screenshot%202022-05-07%20at%207.42.21%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1332" data-original-width="1710" height="498" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbncrpZoVCYcMx_SsEer8SOEp1zbx4bH1LrbgFO7D_lTTFpEpEXXoD2FsOxbs95yx9HO8B4xJqJfCXQjZhh3NE4SeUF-nQurCeofur9Js5OyNDgH947A27nwJk7GvV055Qpuir1ufigGD8Ao-_HBtgOY9CVFjMDfk1zSI1i0Bs_lvmWrE6w4LUZLYv/w640-h498/Screenshot%202022-05-07%20at%207.42.21%20AM.png" width="640" /></a></div><br /><div><b>Update Rating to Warm</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhptia3EZ6iai9yqmcZ28liN844GgrZFuWQSjj_Gq6qeGszak2X-5PSyFyZHDTPlvSPwAaQmoVvAyCZeQUGmyk90U4zHIFtPmjJtXZYvOohyNE5FrmRyCxTAUmN12vxUZQj9nq4OjaPb7XSDwRRaeak4t6sA3fxZnrdOOJciIYVNsufkO0Prmn_xWsn/s1706/Screenshot%202022-05-07%20at%207.42.35%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1326" data-original-width="1706" height="498" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhptia3EZ6iai9yqmcZ28liN844GgrZFuWQSjj_Gq6qeGszak2X-5PSyFyZHDTPlvSPwAaQmoVvAyCZeQUGmyk90U4zHIFtPmjJtXZYvOohyNE5FrmRyCxTAUmN12vxUZQj9nq4OjaPb7XSDwRRaeak4t6sA3fxZnrdOOJciIYVNsufkO0Prmn_xWsn/w640-h498/Screenshot%202022-05-07%20at%207.42.35%20AM.png" width="640" /></a></div><br /></div><div><b>Update Rating to Hot</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG1lZGmm9wF_ql3DQGQ36FZucQHYRYp-gCnkAwaHlwXC7LeOmWO_MoEKrYWrxFk3WGka9jcfIrq7imSqs2p5ZxH_nywpEd537PB49O9vlVQP6brUR4OKEAOnTAdVQnECr55-vI5lAan-1C12E-5sLWpO5bwso_nTCDpCZ-KZcqRHq01jChD4_Q6q1D/s1706/Screenshot%202022-05-07%20at%207.42.46%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1336" data-original-width="1706" height="502" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG1lZGmm9wF_ql3DQGQ36FZucQHYRYp-gCnkAwaHlwXC7LeOmWO_MoEKrYWrxFk3WGka9jcfIrq7imSqs2p5ZxH_nywpEd537PB49O9vlVQP6brUR4OKEAOnTAdVQnECr55-vI5lAan-1C12E-5sLWpO5bwso_nTCDpCZ-KZcqRHq01jChD4_Q6q1D/w640-h502/Screenshot%202022-05-07%20at%207.42.46%20AM.png" width="640" /></a></div><br />Now that we have our update elements, we need to link these 3 update elements with our decision outcomes so that, when: </div><div><ul style="text-align: left;"><li><b>Annual Revenue is less than 25,000 -> Lead Rating = Cold</b></li><li><b>Annual Revenue is between 25,000 and 75,000 -> Lead Rating = Warm</b></li><li><b>Annual Revenue is greater than 75,000 -> Lead Rating = Hot</b></li></ul><div><br /></div><div>This is how our final flow looks like:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIRmo4pJnVivStcW_i6or_Uf54oCGjNyoggiF-dfr9g9f2-U079rgh2UBFp5k0l9iLTD7KH8J_FIqYybt1wqp8Bu5IQa79Vdl0GF-skGmnwhHhIn-I2DzOW-GDzRCyXWXgabN6y16BVtrbzWa7DKKqqntzjXcVlPa7c8z41BqoQlpzdZVDIvLB0POK/s1860/Screenshot%202022-05-07%20at%207.47.22%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1282" data-original-width="1860" height="442" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIRmo4pJnVivStcW_i6or_Uf54oCGjNyoggiF-dfr9g9f2-U079rgh2UBFp5k0l9iLTD7KH8J_FIqYybt1wqp8Bu5IQa79Vdl0GF-skGmnwhHhIn-I2DzOW-GDzRCyXWXgabN6y16BVtrbzWa7DKKqqntzjXcVlPa7c8z41BqoQlpzdZVDIvLB0POK/w640-h442/Screenshot%202022-05-07%20at%207.47.22%20AM.png" width="640" /></a></div><h3 style="text-align: left;">Create a test class for the above flow</h3><div>Now, it's time to create a test class for this flow. While creating a test class for flow, <b>The intent is to cover all the flow elements, while testing various scenarios and verify the result</b>. Let's have a look at the full code for the test class below covering all scenarios and then we'll see each method one by one in detail.</div><div><pre>/*
* Created:- 07/05/2022
* Last Updated:- 07/05/2022
* Description:- This is the test class for Update_Lead_Rating_based_on_Annual_Revenue Flow
*/
@isTest
public with sharing class LeadUpdateFlowTest {
// * Constants
static final Integer NUMBER_OF_RECORDS = 200;
static final String RATING_COLD = 'Cold';
static final String RATING_WARM = 'Warm';
static final String RATING_HOT = 'Hot';
// * Description: This method is used to create initial data for the test methods
@TestSetup
static void makeData() {
List<Lead> leads = new List<Lead>();
for(Integer i=1; i<=NUMBER_OF_RECORDS; i++) {
leads.add(new Lead(LastName = 'Test Lead ' + i, Company='Test Company'));
}
insert leads;
}
/*
* Description: This method is used to verify lead rating
* when the annual revenue is less than 25,000
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueLessThan25kTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 24999;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
}
Test.stopTest();
}
/*
* Description: This method is used to verify lead rating
* when the annual revenue is between 25,000 and 75,000.
* We're keeping the value at floor i.e. 25,000 for this test
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kFloorTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 25000;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
}
Test.stopTest();
}
/*
* Description: This method is used to verify lead rating
* when the annual revenue is between 25,000 and 75,000.
* We're keeping the value at ceiling i.e. 75,000 for this test
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kCeilingTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 75000;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
}
Test.stopTest();
}
/*
* Description: This method is used to verify lead rating
* when the annual revenue is greater than 75,000
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueGreaterThan75kTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 75001;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
}
Test.stopTest();
}
/*
* Description: This method is used to cover all low elements and verify lead rating
* when the annual revenue is less than 25k, between 25k and 75k and also greater than 75k
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueAllElementsTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 20000;
}
update leads;
List<Lead> updatedLeadsWithRatingCold = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeadsWithRatingCold) {
System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
}
for(Lead lead : leads) {
lead.AnnualRevenue = 50000;
}
update leads;
List<Lead> updatedLeadsWithRatingWarm = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeadsWithRatingWarm) {
System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
}
for(Lead lead : leads) {
lead.AnnualRevenue = 80000;
}
update leads;
List<Lead> updatedLeadsWithRatingHot = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeadsWithRatingHot) {
System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
}
Test.stopTest();
}
}</pre></div><b>Now let's understand each method one by one in detail:</b><div><pre> // * Constants
static final Integer NUMBER_OF_RECORDS = 200;
static final String RATING_COLD = 'Cold';
static final String RATING_WARM = 'Warm';
static final String RATING_HOT = 'Hot';
</pre><div>First of all I created some constants where I specified the total number of records my test class will create and also the 3 lead ratings that I need to verify once my flow is executed.</div><div><pre> // * Description: This method is used to create initial data for the test methods
@TestSetup
static void makeData() {
List<Lead> leads = new List<Lead>();
for(Integer i=1; i<=NUMBER_OF_RECORDS; i++) {
leads.add(new Lead(LastName = 'Test Lead ' + i, Company='Test Company'));
}
insert leads;
}
</pre></div><div>Next, I have the <b>makeData()</b> method which is going to create some lead records based on the number we specified in the <b>NUMBER_OF_RECORDS</b> constant.</div><div><pre> /*
* Description: This method is used to verify lead rating
* when the annual revenue is less than 25,000
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueLessThan25kTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 24999;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
}
Test.stopTest();
}
</pre></div><div>Once we have the records created, the first method that's specified above will test the scenario when we have the annual revenue of every lead less than 25,000. So, we updated all the leads with annual revenue as 24,999, we queried the updated leads and verified the lead rating. As per our flow, the lead rating should be <b>Cold</b>.</div><div><br /></div><div>Let's run this test method once and see the results:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3gWHr9B5gaXQLRowihgaEY2e7EoNwDvASWIt4t9cg-idFWHp9somrGGvpf4SHl0vbCPFDvi77EHy6JNuizIZrhi4EFYtjLFYFeSkxb_KciHbohgyIUBrJo7gEuVj9lGIth_VXHQXGHTkVMeqSlVstdxeM18eSLPYICp7NaAogKhZCgffeFgwjyofZ/s1884/Screenshot%202022-05-07%20at%201.00.35%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="210" data-original-width="1884" height="72" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3gWHr9B5gaXQLRowihgaEY2e7EoNwDvASWIt4t9cg-idFWHp9somrGGvpf4SHl0vbCPFDvi77EHy6JNuizIZrhi4EFYtjLFYFeSkxb_KciHbohgyIUBrJo7gEuVj9lGIth_VXHQXGHTkVMeqSlVstdxeM18eSLPYICp7NaAogKhZCgffeFgwjyofZ/w640-h72/Screenshot%202022-05-07%20at%201.00.35%20PM.png" width="640" /></a></div><br /><div>As you can see, the test executed successfully and passed. <b>But Wait!! How can I check the code coverage? OR I should say the flow elements coverage?</b></div><div><b><br /></b></div><div>We can do that using a simple soql query:</div><div><pre style="white-space: pre-wrap;">sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND TestMethodName = 'updateLeadBasedOnAnnualRevenueLessThan25kTest'" --usetoolingapi
</pre></div><div><br /></div><div>As you can see above, I am querying the <b>Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered and NumElementsNotCovered</b> fields of <b>FlowTestCoverage</b> object and I have also added a where condition where I specified the API name of my flow as: <b>FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue'</b> and I also added a condition for my test method that I executed above as: <b>TestMethodName = 'updateLeadBasedOnAnnualRevenueLessThan25kTest'</b>. Note that I am using <b>Tooling API</b> for this query by adding a flag <b>--usetoolingapi</b> because we're performing a query on the flow metadata.</div><div><br /></div><div>The output of the above query is provided below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie002ZnbO5vYIlXcsAyWuz3rkRKWwA6NcZPEO_z9aCc-XNwAF-nI1hiB_8h8meWOuhSxRknxM-abJ2J-Vw0cFkwyyPqasI9cqDOM1MoLgbwKoZoXux2x1_4h01XK4W0OAHMZ1Xnr_zTvMmplWBHSgEXhLlnLHJ_DXJx3-dnCWZTQ3Lyh8Gsjxv7_mB/s2758/Screenshot%202022-05-07%20at%201.10.44%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="286" data-original-width="2758" height="66" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEie002ZnbO5vYIlXcsAyWuz3rkRKWwA6NcZPEO_z9aCc-XNwAF-nI1hiB_8h8meWOuhSxRknxM-abJ2J-Vw0cFkwyyPqasI9cqDOM1MoLgbwKoZoXux2x1_4h01XK4W0OAHMZ1Xnr_zTvMmplWBHSgEXhLlnLHJ_DXJx3-dnCWZTQ3Lyh8Gsjxv7_mB/w640-h66/Screenshot%202022-05-07%20at%201.10.44%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">As you can see above, there are 2 elements of the flow which are covered and 2 elements which are not covered. As per the condition that we checked, we can say that the below two elements of the flow <b><span style="color: red;">encircled in RED</span></b> are covered:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKgQNw0W_dfXIGJ3vSg4WfSoMGiV8ckNj1yYQtdLMIzjq0l10MNzabBhY0MrTIWOTMz2om5thNLFCNcm-4E7mmxQ8VOZEDrE-O88J0-gglARMFeyGGQXXNkU1vSB6EN_xXZHaRDlUW0iVtqk0wSeSV1xcDM-oK4vLfHZl8bKH2wL_gABLZEIAfwh0N/s1760/Screenshot%202022-05-07%20at%201.16.04%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1156" data-original-width="1760" height="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKgQNw0W_dfXIGJ3vSg4WfSoMGiV8ckNj1yYQtdLMIzjq0l10MNzabBhY0MrTIWOTMz2om5thNLFCNcm-4E7mmxQ8VOZEDrE-O88J0-gglARMFeyGGQXXNkU1vSB6EN_xXZHaRDlUW0iVtqk0wSeSV1xcDM-oK4vLfHZl8bKH2wL_gABLZEIAfwh0N/w640-h420/Screenshot%202022-05-07%20at%201.16.04%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">and the other two elements are not covered. As we've verified that the flow is updating the <b>Rating</b> to <b>Cold</b>, therefore, we can say that this update element was covered. Let's jump onto the next method now!</div><div><pre> /*
* Description: This method is used to verify lead rating
* when the annual revenue is between 25,000 and 75,000.
* We're keeping the value at floor i.e. 25,000 for this test
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kFloorTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 25000;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
}
Test.stopTest();
}
</pre></div><div>In the next method that you can see above, we tested the scenario when the annual revenue is between 25,000 and 75,000. We can have 2 edge conditions here i.e. the annual revenue equal to 25,000 and the annual revenue equal to 75,000. We're testing the floor condition here i.e. the minimum value of AnnualRevenue which will satisfy this condition. Therefore, we've set the annual revenue to 25,000 and verified that the rating should be <b>Warm</b>.</div><div><br /></div><div>The output when the above test method is executed is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2AOOTDSiv7ANgT3QhaXFROoOlaGFFqpFDMK0iu4adzOcdMH0SQiJAlaptg9vl5ru5OuWMSI1lq97kZHv1PuIPPaXK1e2E2Q5RVkbMghJMKHmy9nRbJ-V_rLpOVGS0zZ5thEDVeJan_xTAHTgWSm9sN2XfCKK8M8s_flAPZ_gK0HufKKbzMftWICDQ/s2038/Screenshot%202022-05-07%20at%201.20.48%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="228" data-original-width="2038" height="72" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2AOOTDSiv7ANgT3QhaXFROoOlaGFFqpFDMK0iu4adzOcdMH0SQiJAlaptg9vl5ru5OuWMSI1lq97kZHv1PuIPPaXK1e2E2Q5RVkbMghJMKHmy9nRbJ-V_rLpOVGS0zZ5thEDVeJan_xTAHTgWSm9sN2XfCKK8M8s_flAPZ_gK0HufKKbzMftWICDQ/w640-h72/Screenshot%202022-05-07%20at%201.20.48%20PM.png" width="640" /></a></div><div><pre> /*
* Description: This method is used to verify lead rating
* when the annual revenue is between 25,000 and 75,000.
* We're keeping the value at ceiling i.e. 75,000 for this test
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueBetween25kAnd75kCeilingTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 75000;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
}
Test.stopTest();
}
</pre></div><div>Next we tested with the ceiling value or the second edge condition where the annual revenue is set to 75,000. As you can see above, we verified that the rating should be <b>Warm</b> in this case as well. </div><div><br /></div><div>The output when the above test method is executed is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-YAp8ZiLvO9IrQgCF0OZXnB0HAo4YlCfSEtds1HK4HhdM62LbW5I-KYrJrrcMfI-v0aY6CBoqQt_Zqllhqafjjtha1rUSl0xbU5BYM7unrSyWj6g9N8PYGhdLys1stPv6XsdGX3KM2O9dUF6wpJ0xN5SFIUIlQvbFrXpPTiOC1ADUxpiy3YlqYm3U/s2090/Screenshot%202022-05-07%20at%201.21.10%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="248" data-original-width="2090" height="76" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-YAp8ZiLvO9IrQgCF0OZXnB0HAo4YlCfSEtds1HK4HhdM62LbW5I-KYrJrrcMfI-v0aY6CBoqQt_Zqllhqafjjtha1rUSl0xbU5BYM7unrSyWj6g9N8PYGhdLys1stPv6XsdGX3KM2O9dUF6wpJ0xN5SFIUIlQvbFrXpPTiOC1ADUxpiy3YlqYm3U/w640-h76/Screenshot%202022-05-07%20at%201.21.10%20PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Let's see the test coverage for these methods as well. The updated query is given below:</div><div class="separator" style="clear: both; text-align: left;"><pre style="white-space: pre-wrap;">sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND (TestMethodName = 'updateLeadBasedOnAnnualRevenueBetween25kAnd75kFloorTest' OR TestMethodName = 'updateLeadBasedOnAnnualRevenueBetween25kAnd75kCeilingTest')" --usetoolingapi
</pre><div>The result when the above query is executed is shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCy21ElBIaewEFiH3o3fnFw8_CAMjtFKv6od1_3HbNK2G3i3_AvrhBBEJF_OW61r87LdPvC0LZYYZjuFDY9c-z64tBSYnxtR3V66kA_gwTQodE1XFuWgCEtvYsdbmNdHtYALVhCZE5I0bIvtKADLGHsWTdsXFwBFj33WyT_O7fcKP2VfePNsMvFECG/s2766/Screenshot%202022-05-07%20at%201.25.27%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="344" data-original-width="2766" height="80" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCy21ElBIaewEFiH3o3fnFw8_CAMjtFKv6od1_3HbNK2G3i3_AvrhBBEJF_OW61r87LdPvC0LZYYZjuFDY9c-z64tBSYnxtR3V66kA_gwTQodE1XFuWgCEtvYsdbmNdHtYALVhCZE5I0bIvtKADLGHsWTdsXFwBFj33WyT_O7fcKP2VfePNsMvFECG/w640-h80/Screenshot%202022-05-07%20at%201.25.27%20PM.png" width="640" /></a></div>As you can see above, for both the test method executions, two elements of the flow are covered and two are not covered. This time both the methods are dealing with the condition when annual revenue is between 25,000 and 75,000. Therefore, we can say that the below two elements of the flow <b style="color: red;">encircled in RED</b> are covered:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5ycomXGoUuGjbppxWuEAvaUnkYaDUC7_nDb387lAMidXXIaIq1MDDmWK1e8wzB9Tw_RvSYFXWnW_OoABfZU36g-kuft8IT4aMdSYQycVaI82lJGSzpnwApzIS_srkwbVAMInFrYzIyGup3vPsPE_8rVCiHS0LOtNCuXdn0tT6s7gXrA4a0WqaTTJk/s1696/Screenshot%202022-05-07%20at%201.29.48%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1128" data-original-width="1696" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5ycomXGoUuGjbppxWuEAvaUnkYaDUC7_nDb387lAMidXXIaIq1MDDmWK1e8wzB9Tw_RvSYFXWnW_OoABfZU36g-kuft8IT4aMdSYQycVaI82lJGSzpnwApzIS_srkwbVAMInFrYzIyGup3vPsPE_8rVCiHS0LOtNCuXdn0tT6s7gXrA4a0WqaTTJk/w640-h426/Screenshot%202022-05-07%20at%201.29.48%20PM.png" width="640" /></a></div>and the other two elements are not covered from this test method. As we've verified that the flow is updating the <b>Rating</b> to<b> Warm</b>, therefore, we can say that this update element was covered. Let's cover the last update element as well:<div><pre> /*
* Description: This method is used to verify lead rating
* when the annual revenue is greater than 75,000
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueGreaterThan75kTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 75001;
}
update leads;
List<Lead> updatedLeads = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeads) {
System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
}
Test.stopTest();
}
</pre></div><div><br /></div><div>Finally, the last test condition can be, when the annual revenue is greater than 75,000. So, we updated all the leads with annual revenue as 75,001. We queried the updated leads and verified the lead rating. As per our flow, the lead rating should be <b>Hot</b>.</div><div><br /></div><div>The output when the above test method is executed is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_wXUJV6tmvuvisc5RNgf-4OwqjvtK2GBmHwMUrLGJ7iqM3tLXOvP_nIcRmmbzg5bQzCIOo43jGk9z79QlQSPhDKQcv2_qGSViC_YK78C9cKrux4cklMbMAG07bgPlUteEhRq7Kvd5CgABkvA_rmsKSq7SazzMsts1ZhJNH_cGjt4pOuk6OE4ZqMpy/s1990/Screenshot%202022-05-07%20at%201.35.42%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="220" data-original-width="1990" height="70" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_wXUJV6tmvuvisc5RNgf-4OwqjvtK2GBmHwMUrLGJ7iqM3tLXOvP_nIcRmmbzg5bQzCIOo43jGk9z79QlQSPhDKQcv2_qGSViC_YK78C9cKrux4cklMbMAG07bgPlUteEhRq7Kvd5CgABkvA_rmsKSq7SazzMsts1ZhJNH_cGjt4pOuk6OE4ZqMpy/w640-h70/Screenshot%202022-05-07%20at%201.35.42%20PM.png" width="640" /></a></div><br /><div>As you can see above, the method executed successfully, let's see the code coverage for this method as well. I am sharing the updated query below:</div><div><div><pre style="white-space: pre-wrap;">sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND TestMethodName = 'updateLeadBasedOnAnnualRevenueGreaterThan75kTest'" --usetoolingapi
</pre></div><div>The output when this query is executed is given below:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmaXTzWcC5S30LxNib5jJpzE7pdZQMUc6meX1s4EdSfXBDFwmhVwWJDivGu0UebmTBq5uQeosymNxbVTellnvPpoDVrUhXs46fGiddC1DE7ARbT6rArb3rm-ZVhb7VbykMg7AWAg0c6Ic7HIRZ-qvB0h3yXt3K-Clr8rEjKhuuqUlhueNh6TAsGNYm/s2772/Screenshot%202022-05-07%20at%201.38.39%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="294" data-original-width="2772" height="68" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmaXTzWcC5S30LxNib5jJpzE7pdZQMUc6meX1s4EdSfXBDFwmhVwWJDivGu0UebmTBq5uQeosymNxbVTellnvPpoDVrUhXs46fGiddC1DE7ARbT6rArb3rm-ZVhb7VbykMg7AWAg0c6Ic7HIRZ-qvB0h3yXt3K-Clr8rEjKhuuqUlhueNh6TAsGNYm/w640-h68/Screenshot%202022-05-07%20at%201.38.39%20PM.png" width="640" /></a></div><br /><div>As you can see above, once again, two elements of the flow are covered and two elements are not covered. This time our method is dealing with the condition when annual revenue is greater than 75,000. Therefore, we can say that the below two elements of the flow <b style="color: red;">encircled in RED</b> are covered:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB9zL5CZMveyjs3G21bgFk4yc-r3cycRbIddmYzDt9z_XIUMPWvVdO3dgtxzuMBtJoXkRpqMrY_rYbY66c2qiMTQDDx70VRfGIhEgrUr_GVQvD_0Nf2yOU3124dqjGzEQJBrn9kPRHVYd27bAUeRCQFO9HF1uV0VRYPpvXaYkyZBsgq-6J3H15BGnA/s1708/Screenshot%202022-05-07%20at%201.41.35%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1104" data-original-width="1708" height="414" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB9zL5CZMveyjs3G21bgFk4yc-r3cycRbIddmYzDt9z_XIUMPWvVdO3dgtxzuMBtJoXkRpqMrY_rYbY66c2qiMTQDDx70VRfGIhEgrUr_GVQvD_0Nf2yOU3124dqjGzEQJBrn9kPRHVYd27bAUeRCQFO9HF1uV0VRYPpvXaYkyZBsgq-6J3H15BGnA/w640-h414/Screenshot%202022-05-07%20at%201.41.35%20PM.png" width="640" /></a></div>and the other two elements are not covered from this test method. Again we've verified that the flow is updating the <b>Rating</b> to <b>Hot</b>, therefore, we can say that this update element was covered.<br /><div><br /></div><div>We've now covered all the flow elements using different test methods and the results that we verified in our test methods signify that the right elements were covered. You may be thinking that it would be great if we can cover all the flow elements once, just to be sure that we've covered everything. Well, we can do that as well. Let's have a look at the below method:</div><div><pre> /*
* Description: This method is used to cover all low elements and verify lead rating
* when the annual revenue is less than 25k, between 25k and 75k and also greater than 75k
*/
@isTest
public static void updateLeadBasedOnAnnualRevenueAllElementsTest() {
List<Lead> leads = [SELECT AnnualRevenue FROM Lead];
Test.startTest();
for(Lead lead : leads) {
lead.AnnualRevenue = 20000;
}
update leads;
List<Lead> updatedLeadsWithRatingCold = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeadsWithRatingCold) {
System.assertEquals(RATING_COLD, lead.Rating, 'Lead rating should be: ' + RATING_COLD);
}
for(Lead lead : leads) {
lead.AnnualRevenue = 50000;
}
update leads;
List<Lead> updatedLeadsWithRatingWarm = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeadsWithRatingWarm) {
System.assertEquals(RATING_WARM, lead.Rating, 'Lead rating should be: ' + RATING_WARM);
}
for(Lead lead : leads) {
lead.AnnualRevenue = 80000;
}
update leads;
List<Lead> updatedLeadsWithRatingHot = [SELECT Rating FROM Lead];
for(Lead lead : updatedLeadsWithRatingHot) {
System.assertEquals(RATING_HOT, lead.Rating, 'Lead rating should be: ' + RATING_HOT);
}
Test.stopTest();
}
</pre></div><div>As you can see above, this method is used to cover all the scenarios one by one by updating the leads and will also verify the results of the flow updates once all the lead records are updated.</div><div><br /></div><div>The output when the above test method is executed is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG_wUSsJJhcHGlxRN-hmLdeB5meYG96wPrzn6ya8yT_4En1SzqBWd9M7ZFor3fyjPw6kel2doqXObWBq2MeKYeYfdhguls9xy_g2K1DfiWHxJa134tjjDw9TOkKXVounCllQzd-1BTT0-vJsHLcmyNOm3IiODXBR-FXC6FpOveoBS3KLqgyhzu9K0K/s1914/Screenshot%202022-05-07%20at%201.48.49%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="222" data-original-width="1914" height="74" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgG_wUSsJJhcHGlxRN-hmLdeB5meYG96wPrzn6ya8yT_4En1SzqBWd9M7ZFor3fyjPw6kel2doqXObWBq2MeKYeYfdhguls9xy_g2K1DfiWHxJa134tjjDw9TOkKXVounCllQzd-1BTT0-vJsHLcmyNOm3IiODXBR-FXC6FpOveoBS3KLqgyhzu9K0K/w640-h74/Screenshot%202022-05-07%20at%201.48.49%20PM.png" width="640" /></a></div><br /><div>So, our test passed. Now let's see the code coverage for this method using the updated query shared below:</div><div><div><pre style="white-space: pre-wrap;">sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue' AND TestMethodName = 'updateLeadBasedOnAnnualRevenueAllElementsTest'" --usetoolingapi
</pre></div></div><div><div>The output when this query is executed is given below:</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXXkDal01J8r5hq9WOFNnQJ-baL9pD3pADCgdFWaFYQvt5FXZnVMgNAoopK8uoPcuP7azR-dlDcKaoJsSMXascazysxQ3-ixKp_yTeCxuJ08H65VJio0sK8EF_EYmI5bX1UvNoLTOR12D9uBpJLegincmw-GfcMpFlI8g9pYMDixG3g7GcTszETtwc/s2782/Screenshot%202022-05-07%20at%201.50.39%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="298" data-original-width="2782" height="68" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXXkDal01J8r5hq9WOFNnQJ-baL9pD3pADCgdFWaFYQvt5FXZnVMgNAoopK8uoPcuP7azR-dlDcKaoJsSMXascazysxQ3-ixKp_yTeCxuJ08H65VJio0sK8EF_EYmI5bX1UvNoLTOR12D9uBpJLegincmw-GfcMpFlI8g9pYMDixG3g7GcTszETtwc/w640-h68/Screenshot%202022-05-07%20at%201.50.39%20PM.png" width="640" /></a></div><br /><div>As you can see above, this time the total number of elements covered are 4 and the total number of elements not covered are 0. Therefore, we can say that all the 4 elements of the flow were covered this time which are <b style="color: red;">encircled in RED</b> below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqmZMUBpWRGLOTHJCp7wOORDFO_FADUTWsaOK3VQKwOdQb3oviCi1nag8HkqaHBrOvJNBZpCA5ay0T9OA35Ozjn_KpBOppVGsfbCEaLfUNbE_70LlPyHfPRwBFzx2MiCe3cBhK7ix-DwY9Nl1K06NDKz2IhcvsZkzTsMFxfoYVdjsCEyDN1i6xRGry/s1732/Screenshot%202022-05-07%20at%201.53.08%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1042" data-original-width="1732" height="386" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqmZMUBpWRGLOTHJCp7wOORDFO_FADUTWsaOK3VQKwOdQb3oviCi1nag8HkqaHBrOvJNBZpCA5ay0T9OA35Ozjn_KpBOppVGsfbCEaLfUNbE_70LlPyHfPRwBFzx2MiCe3cBhK7ix-DwY9Nl1K06NDKz2IhcvsZkzTsMFxfoYVdjsCEyDN1i6xRGry/w640-h386/Screenshot%202022-05-07%20at%201.53.08%20PM.png" width="640" /></a></div>So, this time, <b>YOU HAVE COVERED THE WHOLE FLOW. CONGRATULATIONS!!!</b></div><div><div><br /></div><div>This is how you can create a test class of the flow and verify the test coverage as well. I am sharing the combined results of all the methods below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjP7IZHpwKj0rQw4sodfkam1_1USVPiOiDlFyi9gzjAwA-m-rPks06GVSmF0YLWOE5YBi0qV7BlDxokTfFWoQkWqzwjFgWxPwBanhVOoWJhtsN_Htksuw9yyf1zUZPBDF2WsK73hgTZawd834Q-X1W9EjfooEhravSqlCfBM-FaRwU5liJW7JboH07A/s2274/Screenshot%202022-05-07%20at%201.55.31%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="262" data-original-width="2274" height="74" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjP7IZHpwKj0rQw4sodfkam1_1USVPiOiDlFyi9gzjAwA-m-rPks06GVSmF0YLWOE5YBi0qV7BlDxokTfFWoQkWqzwjFgWxPwBanhVOoWJhtsN_Htksuw9yyf1zUZPBDF2WsK73hgTZawd834Q-X1W9EjfooEhravSqlCfBM-FaRwU5liJW7JboH07A/w640-h74/Screenshot%202022-05-07%20at%201.55.31%20PM.png" width="640" /></a></div>You can get these results by executing the SOQL query as shown below:</div><div><div><pre style="white-space: pre-wrap;">sfdx force:data:soql:query --query "SELECT Id, ApexTestClassId, TestMethodName, FlowVersionId, NumElementsCovered, NumElementsNotCovered FROM FlowTestCoverage WHERE FlowVersion.Definition.DeveloperName = 'Update_Lead_Rating_based_on_Annual_Revenue'" --usetoolingapi
</pre></div><div>We just removed the <b>TestMethodName</b> AND condition here because we want to query the results of all test methods.</div><div><br /></div><div><b>Note:</b> If you want to make sure that you deploy all the flows to your production environment with good test coverage, you can go to <b>Setup --> Process Automation Settings</b> and select the <b>Deploy processes and flows as active</b> option. Please note that this option will not be available in non-production orgs. If you turn this on, salesforce will make sure that your apex tests have at least 75% coverage of the total number of active processes and active autolaunched flows in your org. If the required percentage isn't met, the deployment will be rolled back. You can learn more about that <a href="https://help.salesforce.com/s/articleView?id=release-notes.rn_forcecom_flow_deploy_as_active.htm&type=5&release=216" rel="nofollow" target="_blank"><span style="color: #2b00fe;">here</span></a>.</div><div><br /></div><div>Now you can make sure that your flows are also covered while performing the deployments.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2O3i_caq1TxrkwNkzLnEZki1oaiZTkoRyjsV-jVy45xfUdFsKmB5qged_sEQNBTUvJDrpbkEXiKtz426Ca4EAQ9YI4KZwzoZaWIYBkYEm0ODO9SmzQbQom2Y6Soj9DU6HwcFuohRpGURfRIlBva1ILqHEhuzeI-_ZnZsvbB2KMxUkL8l9AxXwtFGg/s2424/Screenshot%202022-05-07%20at%202.06.54%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="764" data-original-width="2424" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2O3i_caq1TxrkwNkzLnEZki1oaiZTkoRyjsV-jVy45xfUdFsKmB5qged_sEQNBTUvJDrpbkEXiKtz426Ca4EAQ9YI4KZwzoZaWIYBkYEm0ODO9SmzQbQom2Y6Soj9DU6HwcFuohRpGURfRIlBva1ILqHEhuzeI-_ZnZsvbB2KMxUkL8l9AxXwtFGg/w640-h202/Screenshot%202022-05-07%20at%202.06.54%20PM.png" width="640" /></a></div><div><br /></div><div><div>So, that's all for this tutorial everyone, I hope you liked it and understood how you can create test class for a flow. Let me know your feedback in the comments down below. </div><div><br /></div></div><div><b>Happy Trailblazing!!</b></div><div><br /></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com2tag:blogger.com,1999:blog-8643239839712475364.post-87406682502172355452022-04-11T20:28:00.001-07:002022-04-11T20:28:57.101-07:00Lightning Datatable in LWC | How to create a lightning-datatable in LWC?<p>Hello Trailblazers,</p><p><br /></p><p>In this post we're going to learn how we can create a lightning datatable in lwc. In order to create a lightning datatable we use the <b>lightning-datatable</b> tag where we need to specify 3 attributes:</p><p><br /></p><p><b>key-field</b>: To specify a unique identifier for each row of the table. It'll be a string specifying the key column in the data that we need to use to identify each row.</p><p><b>data</b>: Information to be displayed in the datatable. The value passed to this attribute should be a javascript array.</p><p><b>columns</b>: Information about columns of the datatable. This include column name and other metadata associated with it like: type, fieldName, icon details etc. The value passed to this attribute should be a javascript array as well.</p><p><br /></p><p>Now let's see how we can create a very simple datatable to display employees information. Each employee will have some attributes including: Employee Id, First Name, Last Name, Phone Number and Email Address. We're going to consider these 5 attributes for now and will create a datatable showcasing the data with these columns. Let's create a lightning web component now!!</p><p><br /></p><p>We're going to name our component: <b>employeeDatatable</b>. First things first, we're going to display this component at the homepage, so let's setup that:</p><h3 style="text-align: left;">employeeDatatable.js-meta.xml</h3><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>Employee Datatable</masterLabel>
<description>This datatable is used to display employee records.</description>
<targets>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
</pre><div><br /></div><div>As you can see in the above code snippet, I have specified a target as <b>lighting__Homepage</b> as we want to include this datatable in homepage and the <b>isExposed</b> tag has a value as <b>true</b> because we want to search that component in the lightning app builder. The <b>masterLabel</b> and <b>description</b> attributes are optional but nice to have. You can search the component by it's masterLabel if it's provided, otherwise, you'll search it by it's actual name. Let's move onto html now.</div><h3 style="text-align: left;">employeeDatatable.html</h3><div><pre><template>
<div style="height: 250px;">
<lightning-datatable
key-field="employeeId"
data={employeeData}
columns={employeeColumns}>
</lightning-datatable>
</div>
</template>
</pre></div><div>Above you can see the full html code for our datatable. You don't need anything else to display the data. No tr, td, thead or any more tags. As you can see above, <b>key-field</b> attribute has a value as <b>employeeId</b> because our employeeId will be unique and we can use it to uniquely identify each row. Next our <b>data</b> attribute has a value as <b>employeeData</b> and the <b>columns</b> attribute has the value as <b>employeeColumns</b>. Remember, employeeData will be a javascript array and employeeColumns will also be a javascript array.</div><div><br /></div><div><b>Note</b>: You may have seen code specifying <b>data={data}</b> and <b>columns={columns}</b>. Don't get confused by this. In <b>data={data}</b> the <b>data</b> before the "<b>="</b> sign is the attribute name and the <b>{data}</b> after the <b>"="</b> sign is the attribute value which is nothing but an array defined in our js file which will store the data. Similarly, in <b>columns={columns}</b>, the <b>columns</b> before the <b>"="</b> sign is the attribute name and the <b>{columns}</b> after the <b>"="</b> sign is the attribute value which is again an array defined in our js file which will store the columns information. This is the reason I am using different names for my variables above.</div><div><br /></div><div>Now, let's see the js part!</div><h3 style="text-align: left;">employeeDatatable.js</h3><div><pre>import { LightningElement } from 'lwc';
export default class EmployeeDatatable extends LightningElement {
employeeColumns = [
{ label: 'Employee Id', fieldName: 'employeeId' },
{ label: 'First Name', fieldName: 'firstName' },
{ label: 'Last Name', fieldName: 'lastName' },
{ label: 'Phone Number', fieldName: 'employeePhone', type: 'phone' },
{ label: 'Email Address', fieldName: 'employeEemail', type: 'email' }
];
}
</pre></div><div><br /></div><div>As you can see above, I've defined a javascript array named <b>employeeColumns</b> which we have referred in our html. This is an array of objects where each object is going to have a <b>label</b> and a <b>fieldName</b>. The value of the <b>label will be displayed as the column heading</b> and the value of the <b>fieldName is used to identify what information should be displayed under this column</b>, we'll see this in detail as we define <b>employeeData</b>. For now, let's focus on the columns.</div><div><br /></div><div>By default, each column will have a datatype as text. You can also specify a type attribute to specify the datatype of a particular column. Lightning Datatable will automatically apply the formatting according to the type of the column defined. Isn't it great?</div><div><br /></div><div><b>Note</b>: The fieldName can be anything and doesn't depend on the label or type of the data.</div><div><br /></div><div>In our <b>employeeColumns</b> array above, we've kept the first 3 columns as text and the next two columns have a datatype of <b>phone</b> and <b>email</b> respectively. You can now go to your salesforce homepage. Click on <b>Setup -> Edit Page</b> and you can search for the <b>Employee Datatable</b> component in the lightning appbuilder as shown below: </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZibZTi-ro_4t3ZQgbU6_ITdjD0A6EtDFnXKXaLj6vIK08Yue-eLB1i4bx7tUXBypytvbYb3qASLKbP99Xcsq4qhY7iytWoXBHn9g-2rFjT2iPjvig0BEubTNoMERKYisfOL7T2Jo-oOIjvqwRyhAkZ2Nf458uQLNaHvy38g4mhYE4oXVaXnxgIf6A/s1692/Screenshot%202022-04-11%20at%209.40.48%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1692" data-original-width="604" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZibZTi-ro_4t3ZQgbU6_ITdjD0A6EtDFnXKXaLj6vIK08Yue-eLB1i4bx7tUXBypytvbYb3qASLKbP99Xcsq4qhY7iytWoXBHn9g-2rFjT2iPjvig0BEubTNoMERKYisfOL7T2Jo-oOIjvqwRyhAkZ2Nf458uQLNaHvy38g4mhYE4oXVaXnxgIf6A/w228-h640/Screenshot%202022-04-11%20at%209.40.48%20AM.png" width="228" /></a></div><br /><div>This is coming because we've exposed our lwc component: <b><isExposed>true</isExposed></b> tag in the <b>.js-meta.xml</b> file. You just need to drag and drop this component into the homepage and save it (activate as org default if necessary).</div><div><br /></div><div>Once you've embedded this component in the homepage. It'll look as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5m19W-B3eBEcj9hiVZHD0ZtD4InFcnEGFyDihBWYhef0qLljgS2Vx863X1KtFibFgUxXj1Rc-_1tBjDIbN_K_rwQszy9Ssnpg56xUNv-z1X31vZyP3q-sLqvgpEbtpBl8br3f8p9gycg0TWPZuuMK-20XZAXTGcchFhqzhkV_30r1BCTzUFksBsuH/s1936/Screenshot%202022-04-11%20at%209.46.12%20AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1044" data-original-width="1936" height="346" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5m19W-B3eBEcj9hiVZHD0ZtD4InFcnEGFyDihBWYhef0qLljgS2Vx863X1KtFibFgUxXj1Rc-_1tBjDIbN_K_rwQszy9Ssnpg56xUNv-z1X31vZyP3q-sLqvgpEbtpBl8br3f8p9gycg0TWPZuuMK-20XZAXTGcchFhqzhkV_30r1BCTzUFksBsuH/w640-h346/Screenshot%202022-04-11%20at%209.46.12%20AM.png" width="640" /></a></div><div><br /></div><div>As you can see above, we've defined the columns of our datatable here. The labels are the column names and we don't have any data because we haven't defined the <b>employeeData</b> variable yet in our js file. We have defined a height for this datatable as we've specified <b><div style="height: 250px;"></b> in our html code. <b>div</b> is the parent of our lightning-datatable here and is used to restrict the datatable expansion to a specified height.</div><div><br /></div><div>Now, let's add the data to our datatable as well. For this, we'll define another js array named <b>employeeData</b> as shown below:</div><div><pre><span> </span>employeeData = [
{
employeeId: '1',
firstName: 'Richard',
lastName: 'Hendricks',
employeePhone: '(158) 389-2794',
employeeEmail: 'richard@piedpiper.com'
},
{
employeeId: '2',
firstName: 'Jared',
lastName: 'Dunn',
employeePhone: '(518) 390-2749',
employeeEmail: 'jared@piedpiper.com'
},
{
employeeId: '3',
firstName: 'Erlich',
lastName: 'Bachman',
employeePhone: '(815) 391-2974',
employeeEmail: 'erlich.bachman@piedpiper.com'
}
];</pre></div><div>As you can see above, I have added 3 employee records to my employeeData array. Notice that <b>In each record, the key is exactly the "fieldName" that we mentioned in employeeColumns array</b> before, namely: employeeId, firstName, lastName, employeePhone and employeeEmail. This is most important to understand how the data is mapped to columns. Sharing the below image which should clarify the mappings in a better way:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnfJV_PTZMBh6klinMEzbMBk1doe2eeJFZegKaNf1c32tjl-fgCw5un5cWmbiJkHJq4yA0sOI98O0s6Rd7vZrF_ZV_t_zHLzwCus_U0Rd0LVGde-PhmL-sPaUXX0jtMqfCv7rg_BU7ia4a-pxYXuW-cXKVyH9fOZp6Trs6uUIvl-XrR0lYtagr9cfQ/s1446/Screenshot%202022-04-11%20at%208.44.55%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1294" data-original-width="1446" height="572" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnfJV_PTZMBh6klinMEzbMBk1doe2eeJFZegKaNf1c32tjl-fgCw5un5cWmbiJkHJq4yA0sOI98O0s6Rd7vZrF_ZV_t_zHLzwCus_U0Rd0LVGde-PhmL-sPaUXX0jtMqfCv7rg_BU7ia4a-pxYXuW-cXKVyH9fOZp6Trs6uUIvl-XrR0lYtagr9cfQ/w640-h572/Screenshot%202022-04-11%20at%208.44.55%20PM.png" width="640" /></a></div><div><br /></div><div>The output of the above code is given below:</div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhPJMskrB1ZKl-pjtt7sFlRwhTOIhJqtRjMJb2cMWtlSStEbrC5BC0anDavxth0WtK3c5tanvpik7sArONR8fnUdYvxG39gFkL4oPTIFPf--S-9R3i0kuHuet1gsyrT_FZkB7zBi-NrsGRTMcUM2uKTfP8T6-kLC8Cbks-z1eG4pC98XagmzhBMBff/s1928/Screenshot%202022-04-11%20at%208.49.49%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="1928" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhPJMskrB1ZKl-pjtt7sFlRwhTOIhJqtRjMJb2cMWtlSStEbrC5BC0anDavxth0WtK3c5tanvpik7sArONR8fnUdYvxG39gFkL4oPTIFPf--S-9R3i0kuHuet1gsyrT_FZkB7zBi-NrsGRTMcUM2uKTfP8T6-kLC8Cbks-z1eG4pC98XagmzhBMBff/w640-h240/Screenshot%202022-04-11%20at%208.49.49%20PM.png" width="640" /></a></div><br /><div>As you can see we have 3 entries in our datatable based on the 3 objects (records) in our employeeData array. Notice that the phone number and email address values are formatted automatically as we have mentioned the correct type for those. I am sharing the full js code below for your convinience:</div><div><pre>import { LightningElement } from 'lwc';
export default class EmployeeDatatable extends LightningElement {
employeeColumns = [
{ label: 'Employee Id', fieldName: 'employeeId' },
{ label: 'First Name', fieldName: 'firstName' },
{ label: 'Last Name', fieldName: 'lastName' },
{ label: 'Phone Number', fieldName: 'employeePhone', type: 'phone' },
{ label: 'Email Address', fieldName: 'employeeEmail', type: 'email' }
];
employeeData = [
{
employeeId: '1',
firstName: 'Richard',
lastName: 'Hendricks',
employeePhone: '(158) 389-2794',
employeeEmail: 'richard@piedpiper.com'
},
{
employeeId: '2',
firstName: 'Jared',
lastName: 'Dunn',
employeePhone: '(518) 390-2749',
employeeEmail: 'jared@piedpiper.com'
},
{
employeeId: '3',
firstName: 'Erlich',
lastName: 'Bachman',
employeePhone: '(815) 391-2974',
employeeEmail: 'erlich.bachman@piedpiper.com'
}
];
}</pre></div><div>Once, we add a lot of data to our datatable by filling up employeeData array, it'll look as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8P0bcGv5sdIN_3PH0-pxR1QOnm9LQo_tglOsn_Ph1iGlBk52Kvu1paaLfcHhap68tVWXmtr4MEURK7253bkHmKy1XuikzcaKeKmLOJ_gjFkugpN9axEc31F8LrIGptqayFLKXXx67zMtrUNARUqvYqfz8lZiq0d-ZQaI-CiMvxYaAZvg-hB5l1wHn/s1930/Screenshot%202022-04-11%20at%208.58.57%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="624" data-original-width="1930" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8P0bcGv5sdIN_3PH0-pxR1QOnm9LQo_tglOsn_Ph1iGlBk52Kvu1paaLfcHhap68tVWXmtr4MEURK7253bkHmKy1XuikzcaKeKmLOJ_gjFkugpN9axEc31F8LrIGptqayFLKXXx67zMtrUNARUqvYqfz8lZiq0d-ZQaI-CiMvxYaAZvg-hB5l1wHn/w640-h206/Screenshot%202022-04-11%20at%208.58.57%20PM.png" width="640" /></a></div><div><br /></div><div>In this tutorial, we learned about the basics of lightning datatable and how we can implement our own lightning datatable in lwc. I hope you liked the post, let me know your feedback in the comments down below.</div><br /><div><b>Happy Trailblazing!!</b></div><div><br /></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com4tag:blogger.com,1999:blog-8643239839712475364.post-4218012942367311792022-01-20T08:39:00.010-08:002022-02-08T18:07:54.615-08:00The importance of Data Structures in Salesforce Development | Live Session at Cactusforce 2022<p>Hello Trailblazers,</p><p><br /></p><p>This post consist of the presentation, problem statement and the solution code that was used in the live session on "The importance of Data Structures in Salesforce Development" at Cactusforce 2022. The full session abstract can be viewed <a href="https://www.cactusforce.com/2022-sessions/2022/1/20/xxpsyc8q8bljnfeq7ji0t0h14ckb9o-3zf4b-hetw7-55gst-8smrc-yel26-5c5px-htreb-yj5kn-nm96a-mpd9z-4exxa-3a8mj-4zhnw-6k2jt-tzamx-kpf3n-79xjl-p42tg-sa5rg" target="_blank"><span style="color: #2b00fe;">here</span></a>.</p><p><br /></p><p>In this session, we solved a real life project requirement using an advanced data structure and we also had a look at the performance impact we had by using it and learned why it is better than a brute force solution. So, the next time when you face such a requirement, you'll think at the first place - Which data structure should I use to solve it? and then you'll design and implement the most efficient solution.</p><h3 style="text-align: left;">Session Video</h3><div><iframe src="https://player.vimeo.com/video/672922306?h=c9cbfefca9" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
<p><a href="https://vimeo.com/672922306">The importance of Data Structures in Salesforce Development - Rahul Malhotra</a> from <a href="https://vimeo.com/cactusforce">Marisa Hambleton</a> on <a href="https://vimeo.com">Vimeo</a>.</p></div><h3 style="text-align: left;">Slides</h3><iframe allowfullscreen="" frameborder="0" height="485" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/key/AJf84UJJmDBCig" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="595"> </iframe> <div style="margin-bottom: 5px;"> <strong> <a href="//www.slideshare.net/RahulMalhotra98/the-importance-of-data-structures-in-salesforce-development-live-session-at-cactusforce-2022" target="_blank" title="The importance of Data Structures in Salesforce Development | Live Session at Cactusforce 2022">The importance of Data Structures in Salesforce Development | Live Session at Cactusforce 2022</a> </strong> from <strong><a href="//www.slideshare.net/RahulMalhotra98" target="_blank">Rahul Malhotra</a></strong> </div><h3 style="text-align: left;">Problem Statement</h3><div><div>We have a custom field on account named as: <b>UltimateParentAccount__c</b>, this field is a lookup to account and will specify the ultimate parent of any account in the hierarchy. For example, in the below accounts hierarchy:</div><div><span id="docs-internal-guid-f92028da-7fff-a4e2-42a0-d86e7d7b833e"><img height="354" src="https://lh5.googleusercontent.com/mg4j75KJW8RogZ7fIWncGZCNX-xJlaAExf_f35eVMlwTkYs8pYbqkhP271GKctF0LEVTvWEbkAcDxGEnN9Xh8coo1i8QuWM0nF6AYqQaW1RAOoEu25Pl8HVlEfHVuKRTNsv-LBfGP9tb=w640-h354" width="640" /></span></div><div>In every node: </div><div><ul style="text-align: left;"><li>The letter in black is the current account's name. </li><li>The letter in red represents the parent account (value in ParentId) of the current account</li><li>The letter in blue represents the ultimate parent account for the current account i.e. the value in UltimateParentAccount__c field.</li></ul></div><div><br /></div><div>We have to implement a solution for the event when the current account is updated, to make sure that the UltimateParentAccount__c field for the current account, as well as for the accounts in the hierarchy below have the correct value. We need to take into account, below scenarios for this:</div><div><ol style="text-align: left;"><li>Current account parent is null and is updated to a new value</li><li>Current account parent is populated and is updated to null</li><li>Current account parent is populated and is updated to a new value</li></ol><div><br /></div><div>In all these cases, the UltimateParentAccount__c field should be update to a new value on the current account as well as, on the accounts below the current account.</div></div></div><h3 style="text-align: left;">Solution code snippet</h3><p>Please find all the solution code snippets related to this problem statement below. We've implemented the solution to this problem using N-Ary Trees:</p><p><br /></p><h3 style="text-align: left;">N-Ary Tree</h3><div><pre>/*
* Author:- Rahul Malhotra
* Description:- NAry Tree implementation in apex
* Created Date:- 14-01-2022
* Last Modified:- 19-01-2022
* <span> </span>Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public class NAryTree {
// * Node class
public class Node {
// * Data members
sObject data;
Map<Id, Node> childNodesMap;
// * Member functions
public Node(sObject data) {
this.data = data;
}
public sObject getData() {
return data;
}
public Map<Id, Node> getChildNodesMap() {
return childNodesMap;
}
}
// * Data members
Node root;
/*
* Description: This method is used to return root of the tree
*/
public Node getRoot() {
return root;
}
/*
* Description: This method is used to insert a new node in a tree
*/
public Boolean insertData(sObject currentRecord, sObject parentRecord, Comparator comparator) {
/*
* If root node is NULL, creating a new root node
* and adding assigning it as the root node
*/
if(root==NULL) {
root = new Node(currentRecord);
return true;
}
// * Otherwise, adding the current record to the subtree under the correct parent node
return insertDataRecursive(root, currentRecord, parentRecord, comparator);
}
/*
* Description: This method is used to insert the new node in the tree
* by recursively traversing the tree to find the correct position
*/
private Boolean insertDataRecursive(
Node current,
sObject currentRecord,
sObject parentRecord,
Comparator comparator
) {
/*
* If current node's data is equal to parentRecord,
* adding new node to the current node's child
*/
if(comparator.compare(current.data, parentRecord)) {
if(current.childNodesMap == null) {
current.childNodesMap = new Map<Id, Node>();
}
Node newNode = new Node(currentRecord);
current.childNodesMap.put((Id) currentRecord.get('Id'), newNode);
return true;
}
// * If current node's data is not equal to parentRecord, check in the current node's child subtrees
if(current.childNodesMap!=null) {
List<Node> childNodes = current.childNodesMap.values();
for(Node childNode : childNodes) {
if(insertDataRecursive(childNode, currentRecord, parentRecord, comparator)) {
return true;
}
}
}
// * Otherwise, return false as the node cannot be inserted
return false;
}
/*
* Description: This method is used to delete a tree based on the root node
* provided in the parameter
*/
public void deleteTree(Node root) {
// * If root node is not NULL, proceed ahead
if(root!=NULL) {
// * Get the child nodes for the current node
Map<Id, Node> childNodesMap = root.getChildNodesMap();
// * If there are child nodes present
if(childNodesMap!=NULL) {
/*
* Recursively call the deleteTree() method to delete current child
* node as well as subtrees under these child nodes
*/
for(Node currentNode : childNodesMap.values()) {
deleteTree(currentNode);
}
}
// * Remove the root node from the tree
root = NULL;
}
}
/*
* Description: This method is used to perform a level order traversal
* of the whole tree and display the nodes information
*/
public void levelOrderPrint() {
// * Checking if root is not NULL
if(root!=NULL) {
String result = '\n';
// * Initializing a queue
List<Node> nodesQueue = new List<Node>();
// * Adding root node to the queue
nodesQueue.add(root);
// * Looping while queue is not empty
while(!nodesQueue.isEmpty()) {
// * Getting nodes at current level
Integer nodesAtCurrentLevel = nodesQueue.size();
// * Looping while nodes at current level is more than 0
while(nodesAtCurrentLevel>0) {
// * Getting the current node and removing it from the queue
Node current = nodesQueue.get(0);
nodesQueue.remove(0);
// * Adding current node data to the result
result += current.data.get('Id') + ' ' + current.data.get('Name') + ' -> ' + current.data.get('ParentId') + ' ';
// * Adding the child nodes for the current node to the queue
if(current.childNodesMap!=NULL) {
List<Node> childNodes = current.childNodesMap.values();
for(Node childNode : childNodes) {
nodesQueue.add(childNode);
}
}
// * Decrementing nodes at current level by 1
nodesAtCurrentLevel--;
}
// * Adding a new line to the result for next level
result += '\n';
}
// * Displaying the result
System.debug(result);
}
// * If root is NULL, display error message
else {
System.debug('Tree not found');
}
}
}</pre></div><h3 style="text-align: left;">IdCompare Class</h3><div><pre>/*
* <span> </span>Author:- Rahul Malhotra
* <span> </span>Description:- This helper class is used to compare two records based on their ids.
* Created Date:- 14-01-2022
* Last Modified:- 19-01-2022
* <span> </span>Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public class IdCompare extends Comparator {
public override Boolean compare(sObject o1, sObject o2) {
return (Id) o1.get('Id') == (Id) o2.get('Id');
}
}</pre></div><h3 style="text-align: left;">Comparator Class</h3><div><pre>/*
* Author:- Rahul Malhotra
* Description:- Comparator class to be used in NAry Tree
* Created Date:- 14-01-2022
* Last Modified:- 14-01-2022
* <span> </span> Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public abstract class Comparator {
public abstract Boolean compare(sObject o1, sObject o2);
}</pre></div><h3 style="text-align: left;">Accounts Trigger</h3><div><pre>/*
* <span> </span> Author:- Rahul Malhotra
* <span> </span> Description: This is the trigger on account object
* Created Date:- 14-01-2022
* Last Modified:- 19-01-2022
* <span> </span>Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
trigger AccountsTrigger on Account (after update) {
// * Marking accounts whose parent is updated
List<Account> accountsWithUltimateParentUpdate = new List<Account>();
if(trigger.isAfter && trigger.isUpdate) {
for(Account account : trigger.new) {
if(account.ParentId!=trigger.oldMap.get(account.Id).ParentId) {
accountsWithUltimateParentUpdate.add(account);
}
}
}
/*
* If the parent is updated on some accounts,
* update the ultimate parent on these as well as
* on the child accounts in the hierarchy
*/
if(!accountsWithUltimateParentUpdate.isEmpty()) {
AccountTriggerHandler.updateUltimateParentOnChildAccounts(accountsWithUltimateParentUpdate);
}
}</pre></div><h3 style="text-align: left;">AccountTriggerHandler</h3><div><pre>/*
* <span> </span> Author: Rahul Malhotra
* <span> </span>Description: Trigger handler for Account trigger
* Created Date:- 14-01-2022
* Last Modified:- 19-01-2022
* <span> </span> Code Origin:- SFDC Stop (https://www.sfdcstop.com)
*/
public with sharing class AccountTriggerHandler {
/*
* Description: This method is used to update ultimate parent account on the current account
* as well as the child accounts present in the hierarchy
*/
public static void updateUltimateParentOnChildAccounts(List<Account> accounts) {
NAryTree tree = new NAryTree();
IdCompare idCompare = new IdCompare();
Account masterParentAccount = new Account(Name = 'MasterParentAccount');
tree.insertData(masterParentAccount, NULL, NULL);
for(Account account : accounts) {
/*
* If an account's new parent is also updated, this account's ultimated parent account
* field should get updated automatically in the tree for the parent account.
* So, we don't need to keep it as a part of tree as it'll lead to duplicate nodes
*/
if(
account.ParentId==NULL ||
tree.getRoot().getChildNodesMap()==NULL ||
!tree.getRoot().getChildNodesMap().containsKey(account.ParentId)
) {
tree.insertData(account, masterParentAccount, idCompare);
}
}
Set<Id> accountIdSet = new Set<Id>(tree.getRoot().getChildNodesMap().keySet());
accountIdSet.remove(null);
while(!accountIdSet.isEmpty()) {
List<Account> childAccounts = [SELECT Id, ParentId, Name FROM Account WHERE ParentId IN: accountIdSet];
// * If there are no more accounts to process, break the loop
if(childAccounts.isEmpty()) {
accountIdSet.clear();
}
for(Account account : childAccounts) {
// * If current child account is already present in the tree
if(tree.getRoot().getChildNodesMap().containsKey(account.Id)) {
// * Getting current node
NAryTree.Node tempNode = tree.getRoot().getChildNodesMap().get(account.Id);
// * De-link current node from root node
tree.getRoot().getChildNodesMap().remove(account.Id);
// * Remove current node and it's subtree from the tree
tree.deleteTree(tempNode);
}
// * Inserting the child account in the tree
tree.insertData(account, new Account(Id = account.ParentId), idCompare);
// * Removing the parent account's id from the accountIdSet
accountIdSet.remove(account.ParentId);
// * Adding the child account's id to the set
accountIdSet.add(account.Id);
}
}
// * For debugging purposes - Printing the final tree
tree.levelOrderPrint();
// * Getting the root node from the tree and initial child nodes
NAryTree.Node rootNode = tree.getRoot();
Map<Id, NAryTree.Node> childNodesMap = rootNode.getChildNodesMap();
// * Creating a list of accounts to update
List<Account> accountsToUpdate = new List<Account>();
// * Getting initial accounts with their parent information
List<Account> accountsWithParentInformation = [SELECT Id, Parent.UltimateParentAccount__c FROM Account WHERE Id IN: childNodesMap.keySet()];
// * Looping accounts
for(Account account : accountsWithParentInformation) {
// * Setting up ultimate parent account in the current account and it's descendants
if(account.ParentId!=NULL) {
account.UltimateParentAccount__c = account.Parent.UltimateParentAccount__c !=NULL ? account.Parent.UltimateParentAccount__c : account.ParentId;
accountsToUpdate.addAll(updateUltimateParentByDFSOnCurrentSubtree(childNodesMap.get(account.Id), account.UltimateParentAccount__c));
} else {
account.UltimateParentAccount__c = NULL;
accountsToUpdate.addAll(updateUltimateParentByDFSOnCurrentSubtree(childNodesMap.get(account.Id), account.Id));
}
}
// * Adding current account to accounts to update list
accountsToUpdate.addAll(accountsWithParentInformation);
// * Updating accounts
update accountsToUpdate;
}
/*
* Description: This method is used to update ultimate parent account id
* in all the nodes of a tree starting from the root node which is passed as a parameter
*/
static List<Account> updateUltimateParentByDFSOnCurrentSubtree(NAryTree.Node root, Id ultimateParentAccountId) {
// * Creating a queue to perform DFS and forming a list of accounts to update
List<NAryTree.Node> accountNodesQueue = root.getChildNodesMap()?.values();
List<Account> accountsToUpdate = new List<Account>();
// * If account's queue is not NULL
if(accountNodesQueue!=NULL) {
// * Looping while queue is not empty
while(!accountNodesQueue.isEmpty()) {
// * Getting nodes at current level
Integer nodesAtCurrentLevel = accountNodesQueue.size();
// * Looping while nodes at current level is more than 0
while(nodesAtCurrentLevel>0) {
// * Getting the current node and removing it from the queue
NAryTree.Node current = accountNodesQueue.get(0);
accountNodesQueue.remove(0);
// * Updating the ultimate parent account for the current account
// * and adding it to accounts to update list
Account currentAccount = (Account) current.getData();
currentAccount.UltimateParentAccount__c = ultimateParentAccountId;
accountsToUpdate.add(currentAccount);
// * Getting the child nodes for the current node and adding them to the queue
Map<Id, NAryTree.Node> childNodesMap = current.getChildNodesMap();
if(childNodesMap!=NULL) {
for(NAryTree.Node childNode : childNodesMap.values()) {
accountNodesQueue.add(childNode);
}
}
// * Decrementing nodes at current level by 1
nodesAtCurrentLevel--;
}
}
}
// * Returning the accounts to update list
return accountsToUpdate;
}
}</pre></div><p><br /></p><p><b>Happy Trailblazing..!!</b></p>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-43541832507966514782021-10-24T01:32:00.004-07:002021-10-24T01:32:18.898-07:00System.DmlException: Insert/Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []<p>Hello Trailblazers,</p><p><br /></p><p>In this post we're going to see the solution of the error <b>"System.DmlException: Insert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []"</b> OR <b>"</b><b>System.DmlException:</b><b> Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []"</b></p><p><br /></p><p>The most common scenario for this error is when we're trying to insert both the parent and child records at the same time and we're providing both - the Salesforce ID of the parent object and the parent object record reference with External ID, mentioned in the child record. Ideally we should only provide one out of Salesforce ID of parent object or parent object reference with External ID but not both. Let's have a look at an example.</p><p><br /></p><h2 style="text-align: left;">Inserting Account and Contact record together</h2><div>You must be aware of the fact that we can use external ids to create a parent-child relationship automatically while inserting new records in salesforce. For ex: In case of account and contact, you don't need to insert account and then get it's record id to insert contact, in order to link it with earlier inserted account record. You can leverage external id on account to insert both records in a single transaction. Let's see how:</div><div><br /></div><div><script src="https://gist.github.com/rahulmalhotra/9cc164c64658810afc63fc01612333b7.js"></script></div><div>As you can see in the above snippet, I've created a list of sObjects in which I have added one account record and one contact record. I have created an external id field on account with API name as: <b>Reference_ID__c</b>. In case of contact record, I have referred to the parent account record by populating the relationship field "<b>Account"</b> (will be Field_Name__r in case of custom object) with an instance of account object consisting of external id only. Using this we're telling salesforce that this contact is linked to the account with reference id as 123. The above code snippet worked perfectly fine upon execution and as a result, account and contact records are created in salesforce. <b>Give it a try!!</b></div><div><br /></div><div>Let's do a very small change now:</div><div><br /></div><script src="https://gist.github.com/rahulmalhotra/06eae3f6923cad6d176ef4d67a2e450b.js"></script><div>As you can see above, now we've also specified <b>AccountId</b> in contact record and that too as <b>null</b> which probably should have no impact. Let's execute the code and see what we get:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIYd2_HgnIR05SxzegAgDlCqjIE7tZyG8ufmwbqD2Er_OszCdBykDvHprWBnJmwwpzkPC9ld00kVHajW04VslXC9tHG_6ldStf7HSeD-Bk9MiB9atOVTQyvWEimQQo8gF2AYdQiwshlRg/s2786/Screenshot+2021-10-24+at+1.35.14+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="638" data-original-width="2786" height="146" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIYd2_HgnIR05SxzegAgDlCqjIE7tZyG8ufmwbqD2Er_OszCdBykDvHprWBnJmwwpzkPC9ld00kVHajW04VslXC9tHG_6ldStf7HSeD-Bk9MiB9atOVTQyvWEimQQo8gF2AYdQiwshlRg/w640-h146/Screenshot+2021-10-24+at+1.35.14+PM.png" width="640" /></a></div><br /><div>As you can see we're getting the same error <b>System.DmlException: Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Account and a salesforce id, AccountId: []</b>. </div><div><br /></div><div>As per the post heading error message: "System.DmlException: Insert/Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference <b>Parent</b> and a salesforce id, <b>ParentId</b>: []" </div><div><br /></div><div>Here our <b>Parent</b> is <b>Account</b> and <b>ParentId</b> is <b>AccountId</b> these can be different depending upon your field name and what objects you're dealing with. This simply means:</div><h3 style="text-align: left;"><b>"Whenever we're performing a DML on child records, we cannot specify both the parent reference record, as well as the parent record id together, even if the parent record id is null"</b></h3><div>So, make sure you're only specifying one of these. That's all for this tutorial everyone, I hope you liked it. Let me know your feedback in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing!!</b></div><div><br /></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-21581408465758642512021-10-02T00:57:00.001-07:002021-10-02T00:57:17.979-07:00First error: Exceeded max size limit of 6000000 OR Exceeded max size limit of 12000000 - Solution<p>Hello Trailblazers, in this post we're going to talk about the resolution of a very common error as given below:</p><h3 style="text-align: left;"><b>First error: Exceeded max size limit of 12000000</b> OR <b>First error: Exceeded max size limit of 6000000</b> </h3><p>This error appears when you've exceeded the heap size limit in a single transaction. The heap size limit for a synchronous transaction is 6MB and for an asynchronous transaction is 12MB. Asynchronous transactions basically consists of batch classes, future methods and queueable apex.</p><h3 style="text-align: left;"><b>How to resolve this error?</b></h3><div>You need to reduce the heap size in order to resolve this error. Heap size is basically the memory that your code is taking up while executing. It is the sum of all the memory your variables, maps, sets, lists or any other data structure is consuming. In order to resolve the error you need to reduce the heap size. Below are some tips to help you out:</div><div><br /></div><div>1. Use <b>Limits.getHeapSize()</b> to understand how much heap size (approx) is used in the current transaction upto a particular point.</div><div><br /></div><div>2. Use <b>Limits.getLimitHeapSize()</b> to get the total amout of heap size available in the current transaction.</div><div><br /></div><div>3. Use <b>SOQL For Loops</b>, if you use SOQL for loops the result of the query is given in the batch of 200 records while using a list variable or a single record is returned while using a single instance variable. In both the cases, lesser memory is consumed as we're only storing a maximum 200 records at a time (compared to 50,000 records queried in a single SOQL statement).</div><div><pre>List<Account> accounts = [SELECT Id FROM Account];
// Stores 50,000 records at max - greater heap size</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnACggANH6v2bUw01m14LsLv7OytUOTdWqBC_rjisFJM5lTS-opA0oq-RQvd89hT8pCKaEk8Q_8Elwi4kuHWIHZTVskBUCzt8qARchrCso7cQWnRtFrFDPbXuT1L7bqAoM_ZWBidE6_HA/s1320/Screenshot+2021-10-02+at+1.07.50+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="386" data-original-width="1320" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnACggANH6v2bUw01m14LsLv7OytUOTdWqBC_rjisFJM5lTS-opA0oq-RQvd89hT8pCKaEk8Q_8Elwi4kuHWIHZTVskBUCzt8qARchrCso7cQWnRtFrFDPbXuT1L7bqAoM_ZWBidE6_HA/w640-h188/Screenshot+2021-10-02+at+1.07.50+PM.png" width="640" /></a></div>
<div><pre>for(List<Account> accounts : [SELECT Id FROM Account]) {
}
// Stores 200 records at max - lesser heap size
</pre></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh995t_4_j1li5sP0eXOP4L_Xl6wR4daUTsTWepmNiFfUOvumh2TuY75_O11X3kZMFh-7zl8_h3J1S6uqIFE6YBbJ7A7fFiR2F083jcyBA1B71elZg5WRYDgNeVCihsrfia7vuTTVifg6k/s1230/Screenshot+2021-10-02+at+1.09.24+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="534" data-original-width="1230" height="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh995t_4_j1li5sP0eXOP4L_Xl6wR4daUTsTWepmNiFfUOvumh2TuY75_O11X3kZMFh-7zl8_h3J1S6uqIFE6YBbJ7A7fFiR2F083jcyBA1B71elZg5WRYDgNeVCihsrfia7vuTTVifg6k/w640-h278/Screenshot+2021-10-02+at+1.09.24+PM.png" width="640" /></a></div><div><br /></div>4. <b>Manage your code effectively using helper functions</b> - Avoid class level variables.</div><pre>void callMe()
{
List<Account> accounts = [SELECT Id FROM Account];
System.debug(Limits.getHeapSize()); // Coming as: 321085
}
callme();
System.debug(Limits.getHeapSize()); // Coming as: 1044</pre><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhspz6rLZQMPKpUExvOh-LizplwsycxOzuGNgfjAWKHwZAR1tMHSbduEj6f5PoLIA6KxLjHa13yL_YF6F_SiPuYyQZn8_IavnM2vdmYYY8ZpT2ug42nhIqflQXpMmCLH1sQUhH07e_MwKc/s1048/Screenshot+2021-10-02+at+1.24.29+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="506" data-original-width="1048" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhspz6rLZQMPKpUExvOh-LizplwsycxOzuGNgfjAWKHwZAR1tMHSbduEj6f5PoLIA6KxLjHa13yL_YF6F_SiPuYyQZn8_IavnM2vdmYYY8ZpT2ug42nhIqflQXpMmCLH1sQUhH07e_MwKc/w640-h310/Screenshot+2021-10-02+at+1.24.29+PM.png" width="640" /></a></div><br /><div>As you can see above, the heap size is reduced after the function call is complete because the call stack is now empty and the local variables are destroyed.</div><div><br /></div><div>5. <b>Clear the lists/map/set/variables which are not in use</b> or make them null</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7AiYqKVgmVcaEhXmapgK3wln9WeVmbhcsIIshIxukorquEPzmMk-Bo2C6ADb-5m_hj2CSQ7KE_ZHOJnXpBW2Qg4LBPveVCTFwcg7Pf6qAX4YlJ1EavDBIuoPOtqQTQh1Rv1z0KAsLoEE/s1316/Screenshot+2021-10-02+at+1.12.29+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="1316" height="218" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg7AiYqKVgmVcaEhXmapgK3wln9WeVmbhcsIIshIxukorquEPzmMk-Bo2C6ADb-5m_hj2CSQ7KE_ZHOJnXpBW2Qg4LBPveVCTFwcg7Pf6qAX4YlJ1EavDBIuoPOtqQTQh1Rv1z0KAsLoEE/w640-h218/Screenshot+2021-10-02+at+1.12.29+PM.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgUZG2zFgu9H8oDesxEIYQosm-dmipy0jhc9cfvGh8nAuQ0X-ejRuypf0D_3Z3krmUKAKQU-CQpBQr6xTbSPwSkWP60dwJJeVHcfHpAKplQarzkuyh6G25j79f4uT7-iDM8-bqI_r9c0g/s1298/Screenshot+2021-10-02+at+1.16.06+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="504" data-original-width="1298" height="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgUZG2zFgu9H8oDesxEIYQosm-dmipy0jhc9cfvGh8nAuQ0X-ejRuypf0D_3Z3krmUKAKQU-CQpBQr6xTbSPwSkWP60dwJJeVHcfHpAKplQarzkuyh6G25j79f4uT7-iDM8-bqI_r9c0g/w640-h248/Screenshot+2021-10-02+at+1.16.06+PM.png" width="640" /></a></div><div><br /></div><div>These are some of the tips that can be used to prevent the heap size limit error. Make sure to write effective code when working with large data. If you have any other suggestion, do comment it down below and I'll include that in the blog post. </div><div><br /></div><div>That's all for this tutorial everyone I hope you liked it, the full code used in this post can be found <a href="https://gist.github.com/rahulmalhotra/460dbed6a26090d0bc70940cecd20208" target="_blank"><span style="color: #2b00fe;">here</span></a>. Do let me know your feedback/thoughts in the comments down below.</div><div><br /></div><div>Sharing a Salesforce Help Article related to this error: <a href="https://help.salesforce.com/s/articleView?id=000321537&type=1"><span style="color: #2b00fe;">https://help.salesforce.com/s/articleView?id=000321537&type=1</span></a></div><div><br /></div><div><b>Happy Trailblazing!!</b></div><div><br /></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-38807638441741362312021-09-10T21:30:00.002-07:002021-10-02T03:45:43.817-07:00List Data Structure in Apex | Apex Data Structure Tutorials by SFDC Stop<p>Hello Trailblazers,</p><p><br /></p><p>In this post, we're going to learn about how we can use list data structure in apex. A list is a collection of elements or records that you want to store. List in apex can store elements of any data type. It can store: Integer, String, Boolean and any custom data type as well. For example: Account, Contact, Employee__c (Custom Object) etc.</p><h3>Tutorial Video</h3><h3 style="clear: both;"><p style="font-size: medium; font-weight: 400;"></p></h3><h3></h3><h3 style="clear: both;"><p style="font-size: medium; font-weight: 400;"></p><p style="-webkit-text-stroke-width: 0px; font-size: medium; font-weight: 400;"><iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="400" src="https://www.youtube.com/embed/FgVI-58TXXI" width="100%"></iframe></p></h3><p>The syntax to create a new list is as follows:</p><pre>List<DataType> listName = new List<DataType>();</pre><p><br /></p><p>Here, <b>DataType</b> should be replaced by the type of data you would like to store and <b>listName</b> should be replaced by the name of the list. For example, if I want to create a list of integers, I can create it as follows:</p><pre>List<Integer> numbers = new List<Integer>();</pre><p>The above code snippet will create a list of <b>Integers</b> named as <b>numbers</b>. In this list you can store any number of integers. If you want to add elements to the list at the same moment when a list is initialized, you can do that as well. Consider the below examples:</p><h3 style="text-align: left;">1. Creating a list of Integers</h3><p><b><br /></b></p><p><b>Code Snippet:</b></p><pre>List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);</pre><p><b><br /></b></p><p><b>Output:</b></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhw5W212XJDi1I1m9WQEc7FN05mufwjDmCDQx2Xh5_m8QB3HYCXm-ZiUIZk4i8GULfUHlfobahsHIC4tJcRntWGBtSdxl5wjQGPwQMwKrz0907zVDzfgE6ey1u3HlelIdSv6aXUnUeg4b4/s1138/Screenshot+2021-07-29+at+12.48.21+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="468" data-original-width="1138" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhw5W212XJDi1I1m9WQEc7FN05mufwjDmCDQx2Xh5_m8QB3HYCXm-ZiUIZk4i8GULfUHlfobahsHIC4tJcRntWGBtSdxl5wjQGPwQMwKrz0907zVDzfgE6ey1u3HlelIdSv6aXUnUeg4b4/w640-h264/Screenshot+2021-07-29+at+12.48.21+PM.png" width="640" /></a></div><h3 style="text-align: left;">2. Creating a list of Strings</h3><p><b><br /></b></p><p><b>Code Snippet:</b></p><pre>List<String> names = new List<String>{'Richard', 'Gilfoyle', 'Dinesh', 'Jared', 'Monica'};
System.debug(names);</pre><p><b><br /></b></p><p><b>Output:</b></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV8ASyC5FYXi4O-HvzunxXfpVw9knOZ8DDzEcOV_jeNec4KUJ0QusOsl8GqU0wefSnA9j8PBqx3SmmYKmoQifo9y_h6GK9RJa0r0GgEsho3FIy6XsXLs9AWFF85aoPsOuI9vL99s81zx4/s1350/Screenshot+2021-07-29+at+12.51.00+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="454" data-original-width="1350" height="216" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjV8ASyC5FYXi4O-HvzunxXfpVw9knOZ8DDzEcOV_jeNec4KUJ0QusOsl8GqU0wefSnA9j8PBqx3SmmYKmoQifo9y_h6GK9RJa0r0GgEsho3FIy6XsXLs9AWFF85aoPsOuI9vL99s81zx4/w640-h216/Screenshot+2021-07-29+at+12.51.00+PM.png" width="640" /></a></div><p><br /></p><p>In both the above examples, you can see that we've used curly braces {} to specify a comma separated set of values and that values are automatically added to the list. Using this way, you can initialize a list with a set of values. But what if you need to add some more data later on? Let's consider that now:</p><h2 style="text-align: left;">Adding elements to the list</h2><div>You can use the <b>add()</b> method to add elements to the list. Let's consider the below example:</div><div><br /></div><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
System.debug(numbers);</pre><div><b><br /></b></div><div><b>Output:</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipPtcXAAISoqSd4NlwsCvqxXhMXUAt9o-vFuK7fHkcMZ3OZUmh62gsZuj5nWkhmdXLwt3uMPycucIKLTDTY2tZIApLpIM9E4SW1hga6phdzR0krlW_X-Ql-YgtxGBQPjSZy_9KAv8Z1AM/s922/Screenshot+2021-07-29+at+1.01.03+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="922" data-original-width="876" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipPtcXAAISoqSd4NlwsCvqxXhMXUAt9o-vFuK7fHkcMZ3OZUmh62gsZuj5nWkhmdXLwt3uMPycucIKLTDTY2tZIApLpIM9E4SW1hga6phdzR0krlW_X-Ql-YgtxGBQPjSZy_9KAv8Z1AM/w608-h640/Screenshot+2021-07-29+at+1.01.03+PM.png" width="608" /></a></div><b><br /></b></div><div>As you can see above, first of all I initialized a list and then added 5 elements to the list one by one using <b>add()</b> method. Here also, our list is created successfully with 5 elements: 1, 2, 3, 4 and 5.</div><div><br /></div><h2 style="text-align: left;">Accessing elements from the list</h2><div>Each element in a list is associated with an <b>index</b> starting from 0, for example: the list above contains 5 elements which are stored in the memory as follows:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP7zwQ04oYKHcpW6l5hfCyz9qMa7Ht9m2YnmffHOiVVi-0qDikElUMqKnFFLn7b_PxYu61zOXETa9M0bGVl99db2cdWt7wtVTYCaJ3B8RHmlSliUpbgXZj0D8LQ5Qet2n-_cOY85uejow/s876/Screenshot+2021-07-29+at+1.23.27+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="274" data-original-width="876" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP7zwQ04oYKHcpW6l5hfCyz9qMa7Ht9m2YnmffHOiVVi-0qDikElUMqKnFFLn7b_PxYu61zOXETa9M0bGVl99db2cdWt7wtVTYCaJ3B8RHmlSliUpbgXZj0D8LQ5Qet2n-_cOY85uejow/w640-h200/Screenshot+2021-07-29+at+1.23.27+PM.png" width="640" /></a></div><div>Let's say you want to access element at index 3, you can simply refer to that by: <b>numbers[3]</b> or you can also use the <b>get()</b> method as: <b>numbers.get(3)</b>. Considering the above list, <b>the element at index 3 is: 4</b>. Let's try to code this and see the output:</div><div><br /></div><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Element at index 3 = ' + numbers[3]);
System.debug('Element at index 3 using get() = ' + numbers.get(3));</pre><div><b><br /></b></div><div><b>Output:</b></div><div><b><br /></b></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG8Wz8Q0sSWtY1Ln9LCkPDGBp83O1r3R5pMzJGMJqL-ZgyEL7A1Or33bNBQfKqxH2ukWfHg-miJkHQC0ibGaY205y7BNp-zRKqy0i0nhx5RP1gyjm33bkJ0tEivlv8S3BJQZUW0hQCBhc/s1110/Screenshot+2021-07-29+at+1.29.03+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="508" data-original-width="1110" height="292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiG8Wz8Q0sSWtY1Ln9LCkPDGBp83O1r3R5pMzJGMJqL-ZgyEL7A1Or33bNBQfKqxH2ukWfHg-miJkHQC0ibGaY205y7BNp-zRKqy0i0nhx5RP1gyjm33bkJ0tEivlv8S3BJQZUW0hQCBhc/w640-h292/Screenshot+2021-07-29+at+1.29.03+PM.png" width="640" /></a></div>As you can see above, I am getting the correct value i.e. 4 in the debug while accessing the element at index 3 by <b>square brackets []</b> as well as by using the <b>get()</b> method.<div><br /><h2 style="text-align: left;">Adding element at a particular index</h2><p>If you want to add element at a particular index, you can use the <b>add()</b> method to do so. Just pass the <b>index as the first parameter</b> and <b>the value as the second parameter</b> to the add() method. For example:</p><p><br /></p><p><b>Code Snippet:</b></p><pre>List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Adding element "10" at index 3');
numbers.add(3, 10);
System.debug('Updated List = ' + numbers);</pre><p><b><br /></b></p><p><b>Output:</b></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1YUSaiGAHlzmnkBOrPUAYrnK3P7TFeJqXKy-67TXsix3HY15GK5Z5xWvCSVLbAuxKpFxXxwfyFfdV7BNAy9HS2bSEVtu_0ebyAgUucYHK3L7pKIlOI8_MVtgqpKgRlPq2tP2iq8bfFs8/s1108/Screenshot+2021-07-29+at+1.37.17+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="612" data-original-width="1108" height="354" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1YUSaiGAHlzmnkBOrPUAYrnK3P7TFeJqXKy-67TXsix3HY15GK5Z5xWvCSVLbAuxKpFxXxwfyFfdV7BNAy9HS2bSEVtu_0ebyAgUucYHK3L7pKIlOI8_MVtgqpKgRlPq2tP2iq8bfFs8/w640-h354/Screenshot+2021-07-29+at+1.37.17+PM.png" width="640" /></a></div>As you can see above, initially the list was: 1, 2, 3, 4, 5. The elements and their indexes for the list are shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjovgG9kQ9evTTdRTJd8pqBFCBh_zD349gCS8jA8HPtDaTU0EtgAgp0PqbzsSdpkgJr5J_29-YFwCKw3SVLSB9KFX1FZjYUsxK8vQfmgZmyAYmBR32aVQ9MYEkiNIVXmC5cnj-Ia7aeuf0/s876/Screenshot+2021-07-29+at+1.23.27+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="274" data-original-width="876" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjovgG9kQ9evTTdRTJd8pqBFCBh_zD349gCS8jA8HPtDaTU0EtgAgp0PqbzsSdpkgJr5J_29-YFwCKw3SVLSB9KFX1FZjYUsxK8vQfmgZmyAYmBR32aVQ9MYEkiNIVXmC5cnj-Ia7aeuf0/w640-h200/Screenshot+2021-07-29+at+1.23.27+PM.png" width="640" /></a></div>Then, we're going to add a new element with value 10 at index 3, so, the values 4 and 5 will be shifted ahead to index 4 and 5 respectively and the updated list will be:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuVdxEDNZ7E7LKelCqxJqON738VuXeLv_VjMp_MrWSt3nLxL5IrV2Q3yZAsZbwtfvT5-_pIDnaX4FP82z3ZuD0G-c5spcaC1eJEZvuhVsFCLsVEj6JawZflWR8USbIvc8c1lAN6nNN68s/s970/Screenshot+2021-07-29+at+1.48.16+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="970" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuVdxEDNZ7E7LKelCqxJqON738VuXeLv_VjMp_MrWSt3nLxL5IrV2Q3yZAsZbwtfvT5-_pIDnaX4FP82z3ZuD0G-c5spcaC1eJEZvuhVsFCLsVEj6JawZflWR8USbIvc8c1lAN6nNN68s/w640-h198/Screenshot+2021-07-29+at+1.48.16+PM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><h2 style="text-align: left;">Removing element from the list</h2>We can remove an element from the list by using the <b>remove()</b> method. The remove() method accepts the index of the element that you want to remove. For example: If in the above list you want to remove element 10, then you can pass it's index i.e. 3 in the remove method as follows:<div><br /></div><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>{1,2,3,10,4,5};
System.debug(numbers);
System.debug('Removing element "10" from index 3');
numbers.remove(3);
System.debug('Updated List = ' + numbers);</pre><div><b><br /></b></div><div><b>Output:</b></div><div><b><br /></b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFfviyNdH1kXYgZ_HVghBLit5UDCw5yt6SPEMvJyP34NgcEkW1o0YP9akXC3jfDoygnoey2x5qErzwChOnJmvF5FKWPxgYb9XEsjV9epnnBlXIimr4ooQk_0QqxXwoh1SoSVm2bwcD3rk/s1126/Screenshot+2021-07-29+at+1.56.31+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="496" data-original-width="1126" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFfviyNdH1kXYgZ_HVghBLit5UDCw5yt6SPEMvJyP34NgcEkW1o0YP9akXC3jfDoygnoey2x5qErzwChOnJmvF5FKWPxgYb9XEsjV9epnnBlXIimr4ooQk_0QqxXwoh1SoSVm2bwcD3rk/w640-h282/Screenshot+2021-07-29+at+1.56.31+PM.png" width="640" /></a></div><div><div>These were the basic methods using which you can interact with the list data structure. There are some other methods as well. I am going to discuss those quickly below with a one line description and code snippets.</div><div><br /></div><h2 style="text-align: left;">Other Commonly used List methods</h2><div><h3 style="text-align: left;">clear() - Clear all elements in the list</h3><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Clearing list...');
numbers.clear();
System.debug('Updated list = ' + numbers);</pre><div><b><br /></b></div><div><b>Output:</b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisQrUtwCYu1iJ-bxJJLeNUlvGQgiAILRxt8zjaX9SunzCdc8AFfh2mpu5jEzVTP5lPYWsUQhWKCdExTReSn9i4-6k8ZILUaXQfVcNd0h5GV6PxbmKpx76iZbVztv7Su4tPj3M52qs_brI/s1048/Screenshot+2021-07-29+at+3.19.29+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="610" data-original-width="1048" height="372" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisQrUtwCYu1iJ-bxJJLeNUlvGQgiAILRxt8zjaX9SunzCdc8AFfh2mpu5jEzVTP5lPYWsUQhWKCdExTReSn9i4-6k8ZILUaXQfVcNd0h5GV6PxbmKpx76iZbVztv7Su4tPj3M52qs_brI/w640-h372/Screenshot+2021-07-29+at+3.19.29+PM.png" width="640" /></a></div></div><div style="text-align: left;">As you can see above, we created a list of numbers, we used <b>clear()</b> method to clear the list. Then we displayed the list got a confirmation that it's empty.</div><h3 style="text-align: left;">isEmpty() - Returns true if the list is empty, otherwise returns false</h3><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>();
System.debug('List Empty --> ' + numbers.isEmpty());
System.debug('Adding "1" to the list');
numbers.add(1);
System.debug('List Empty --> ' + numbers.isEmpty());</pre><div><b><br /></b></div><div><b>Output:</b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWLCecLdlx2FPtPjOlsLvVvsFEHENVbPhRYCCmeT6tX-yVbPb1IlGv5J8yiYS1VSRQY7NunpICaaXb4HFxjLMAxdmKXyMI58EfTjxlGz7hFDJ6oRbzB_i2Z9hyVLqaFW7tg4236rkttwA/s1072/Screenshot+2021-07-29+at+3.30.30+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="496" data-original-width="1072" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWLCecLdlx2FPtPjOlsLvVvsFEHENVbPhRYCCmeT6tX-yVbPb1IlGv5J8yiYS1VSRQY7NunpICaaXb4HFxjLMAxdmKXyMI58EfTjxlGz7hFDJ6oRbzB_i2Z9hyVLqaFW7tg4236rkttwA/w640-h296/Screenshot+2021-07-29+at+3.30.30+PM.png" width="640" /></a></div></div><div style="text-align: left;">As you can see above, initially, the list was empty, so the <b>isEmpty()</b> method is returning <b>true</b> when called on the numbers list. After that, we added "1" to the list and then the <b>isEmpty()</b> method is returning <b>false</b>.</div><h3 style="text-align: left;">size() - Returns the size of the list i.e. the total number of elements present in the list</h3><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Size of list = ' + numbers.size());</pre><div><b><br /></b></div><div><b>Output:</b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLFF_xATArXVLP7JOGkbr6ROy3tzVFPL0Klxnv9NlP0Z_Ncb4G4banv7NgOBxjelCunBEd4e6qUiNyzbvP_fr_UtEDW_2BECvCzCzflpzEe7ReTNY7OQ3dQ63qjVOZP0RDlCNqF9jMTU0/s1114/Screenshot+2021-07-29+at+3.34.29+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="418" data-original-width="1114" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLFF_xATArXVLP7JOGkbr6ROy3tzVFPL0Klxnv9NlP0Z_Ncb4G4banv7NgOBxjelCunBEd4e6qUiNyzbvP_fr_UtEDW_2BECvCzCzflpzEe7ReTNY7OQ3dQ63qjVOZP0RDlCNqF9jMTU0/w640-h240/Screenshot+2021-07-29+at+3.34.29+PM.png" width="640" /></a></div>As you can see above, I have 5 elements in my list of numbers, so, the size of list is coming out to be 5.<b><br /></b></div><h3 style="text-align: left;">contains() - Returns true if a particular element is present in the list, otherwise, returns false</h3><div><div><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('3 is present in the list --> ' + numbers.contains(3));
System.debug('Removing 3 from index 2');
numbers.remove(2);
System.debug(numbers);
System.debug('3 is present in the list --> ' + numbers.contains(3));</pre><div><b><br /></b></div><div><b>Output:</b></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV_z1_w92RsixbQmQDHLxg_cSvW6a-MUihz22BFXFyOug9xGfm9-fGTyLirSD7H8Q-zUJtD0VzKBN05DQIPiZc7LosNktW8RvoVGWcOOYnBUbxxg9RwEc7rRLIFhM-iayHkF0YkLHP6FM/s1282/Screenshot+2021-07-29+at+3.41.08+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="698" data-original-width="1282" height="348" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV_z1_w92RsixbQmQDHLxg_cSvW6a-MUihz22BFXFyOug9xGfm9-fGTyLirSD7H8Q-zUJtD0VzKBN05DQIPiZc7LosNktW8RvoVGWcOOYnBUbxxg9RwEc7rRLIFhM-iayHkF0YkLHP6FM/w640-h348/Screenshot+2021-07-29+at+3.41.08+PM.png" width="640" /></a></div>As you can see above, we had 5 elements in the list initially: 1, 2, 3, 4, 5. We checked for element 3 using <b>contains()</b> and it returned <b>true</b> as 3 was present in the list. The element 3 was present at index 2 so, we removed 3 from the list by using remove() method and the updated list is: 1, 2, 4, 5. Finally, we checked for element 3 again if it's present in the list using <b>contains()</b> and this time, the result comes out to be <b>false</b>.<b><br /></b><h3 style="text-align: left;">sort() - Sort the elements in the list in ascending order</h3><div style="text-align: left;"><b>Code Snippet:</b></div><pre>List<Integer> numbers = new List<Integer>{1,4,5,3,2};
System.debug(numbers);
System.debug('Sorting list...');
numbers.sort();
System.debug(numbers);</pre><div style="text-align: left;"><b><br /></b></div><div style="text-align: left;"><b>Output:</b></div><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9myU0Z74vIjnCHO7Nifsn9trUwn761BvJiTeFbyRPSc1vKnAW5FOeS8g0idCvS_70Yw4wUi4wf5Z-FDPhyphenhyphentmAVYuezgNxFskjMGgbUNFWoccpdWJJZOLpsTeGt9f-e6fmYYdhcYlbmCQ/s1056/Screenshot+2021-07-29+at+3.44.41+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="550" data-original-width="1056" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9myU0Z74vIjnCHO7Nifsn9trUwn761BvJiTeFbyRPSc1vKnAW5FOeS8g0idCvS_70Yw4wUi4wf5Z-FDPhyphenhyphentmAVYuezgNxFskjMGgbUNFWoccpdWJJZOLpsTeGt9f-e6fmYYdhcYlbmCQ/w640-h334/Screenshot+2021-07-29+at+3.44.41+PM.png" width="640" /></a></div>As you can see above, initially, the list had elements: 1, 4, 5, 3, 2. Then we sorted the list using <b>sort()</b> and now, the list consist of elements in sorted order i.e.: 1, 2, 3, 4, 5.<b><br /></b></div><div style="text-align: left;"><br /></div><p>These are the most common methods that are used while interacting with a list, although there are some other methods as well, I would suggest to have a look at the <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_list.htm" target="_blank"><span style="color: #2b00fe;">official salesforce documentation</span></a> for more information.</p><p>That's all for this tutorial everyone, I hope you liked it. Let me know your feedback or if you have any queries in the comments down below. You can find the whole code snippet used in this tutorial <a href="https://github.com/rahulmalhotra/apex-data-structures/blob/list/scripts/apex/list.apex" target="_blank"><span style="color: #2b00fe;">here</span></a>.</p><p><br /></p><p><b>Happy Trailblazing..!!</b></p></div></div></div></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com2tag:blogger.com,1999:blog-8643239839712475364.post-79084307391187085992021-08-30T21:07:00.002-07:002021-09-09T04:07:31.802-07:00Custom validation in Lightning Web Component | Understanding Regular Expressions (Regex)Hello Trailblazers,<div><br /></div><div>In this post we're going to learn how we can apply custom validation to fields in LWC. We're going to create a simple New Contact Form and then we'll apply validation to each field one by one. Let's get started and have a look at the basic form below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDf_OEIrWNKvdVhWk9EcaTDXk-uMuqtUVepseID6ls0kL7fjJ6wzT0CPYsbnOn1uEUf-8rKu0m6hYQWxxJrq-w6TT46qz8IA8lTpOTIWycPDakZM8aKW8oLEySZ26VMvJ8IDDeIjo-nW0/s1138/Screenshot+2021-07-31+at+1.23.05+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="772" data-original-width="1138" height="434" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDf_OEIrWNKvdVhWk9EcaTDXk-uMuqtUVepseID6ls0kL7fjJ6wzT0CPYsbnOn1uEUf-8rKu0m6hYQWxxJrq-w6TT46qz8IA8lTpOTIWycPDakZM8aKW8oLEySZ26VMvJ8IDDeIjo-nW0/w640-h434/Screenshot+2021-07-31+at+1.23.05+PM.png" width="640" /></a></div><h3>Tutorial Video</h3><h3 style="clear: both;"><p style="font-size: medium; font-weight: 400;"></p></h3><h3></h3><h3 style="clear: both;"><p style="font-size: medium; font-weight: 400;"></p><p style="-webkit-text-stroke-width: 0px; font-size: medium; font-weight: 400;"><iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="400" src="https://www.youtube.com/embed/aDV8ipjgR1U" width="100%"></iframe></p></h3><h3 style="clear: both; text-align: left;">HTML Code</h3><pre><template>
<lightning-card title="Sign Up">
<p class="slds-var-p-around_small">
<lightning-layout multiple-rows>
<lightning-layout-item padding="around-small" size="12" medium-device-size="6" large-device-size="6">
<lightning-input name="firstName" class="validate" type="text" label="First Name" required></lightning-input>
<lightning-input name="password" class="validate" type="password" label="Password" required></lightning-input>
<lightning-input name="email" class="validate" type="email" label="Email" required></lightning-input>
<lightning-combobox name="country" class="validate" label="Country" options={countryOptions} required></lightning-combobox>
</lightning-layout-item>
<lightning-layout-item padding="around-small" size="12" medium-device-size="6" large-device-size="6">
<lightning-input name="lastName" class="validate" type="text" label="Last Name" required></lightning-input>
<lightning-input name="company" class="validate" type="text" label="Company" required></lightning-input>
<lightning-input name="phone" class="validate" type="tel" label="Phone" required></lightning-input>
<lightning-input name="pincode" class="validate" type="number" label="Pin Code"></lightning-input>
</lightning-layout-item>
<lightning-layout-item size="12" class="slds-var-p-top_small">
<lightning-button class="slds-align_absolute-center" label="Sign Up" onclick={createContact}></lightning-button>
</lightning-layout-item>
</lightning-layout>
</p>
</lightning-card>
</template></pre><div>As you can see above, we have a contact form which is used to create a new record based on the information entered. We have a couple of fields here namely: First Name, Last Name, Password, Company, Email, Phone, Country and Pin Code. Some validations are pretty basic and easier to apply. For example: I want some of the fields to be required, so I have mentioned the <b>required</b> property while defining lightning-input to make them required. Notice that we don't have to add something like <b>required="true"</b> here because required is a boolean property and we can set it as true by just mentioning it in the tag.</div><div><br /></div><div>Another thing to notice here is, all the fields that I want to validate are specified with a class named <b>validate</b> because I am going to use the same class name to refer these fields in JavaScript in order to perform the validation. Each of the lightning-input is having some general attributes here like: name, type, label etc. The <b>lightning-combobox</b> that we have in the form is used by the user to select a country from a list of countries, that's why it's referring to <b>countryOptions</b> which we're going to define in js, in the <b>options</b> property. Finally, the lightning-button is going to call <b>createContact()</b> method in js when it's clicked.</div><div><br /></div><div>Before moving onto the js, let's add more validation for some of the fields:</div><h3 style="text-align: left;">Setting up validation for Email field</h3><div>Let's start with the email field, as of now we're checking that the user should enter a text in the email field and that's it. But we need to make sure that the email entered is a valid email and for that, we need to create a regex (regular expression) with which we can match the entered text to ensure if it's correct or not.</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><b>Building a Regex</b></div><div><br /></div><div>Building a simple regex is fairly easy. First of all let's list down the criterias that our email field value should fulfil:<br /><br /></div><div><ol style="text-align: left;"><li>Email can start with characters between A-Z, a-z, 0-9, can have an underscore "<b>_"</b>, a hyphen <b>"-"</b>, or a dot <b>"."</b> in between. For example: rahul.malhotra, RAHUL.MALHOTRA, rahul123.malhotra, rahul_malhotra, rahul-malhotra etc.<br /><br /></li><li>After that it should be followed by an <b>@</b> symbol and we should allow only these characters post @: a-z, 0-9 and a hyphen <b>"-"</b>. For example: rahul.malhotra@example, rahul.malhotra@sfdc12, rahul.malhotra@sfdc-stop etc.<br /><br /></li><li>Finally, we should have a dot "<b>."</b> followed by a minimum of 2 characters in the range: a-z. For example: rahul.malhotra@sfdcstop.com, rahul@example.in etc.</li></ol></div><div><br /></div><div>So, that's how the full email address should be formed fulfilling these conditions. Let's start creating regex now:</div><div><br /></div><div>1. The first point specifies, I can include capital A to Z, small a to z, numbers 0-9 and some special characters like: "_", "-" and "." So, the regex will be: "<b>[A-Za-z0-9._-]+</b>". If you notice all the acceptable characters are included in square followed by a <b>+</b> which means that we can have <b>1 or more occurence</b> of any of these characters. This regex will validate the part of email before @ symbol.<br /><br />2. Then we should have an @ symbol, let's add that to regex so it becomes: "<b>[A-Za-z0-9._-]+@"</b> and as per point 2 in the previous points, I can have only small alphabets a to z and numbers 0 to 9 including a hyphen after @. So, the regex for that will be: "<b>[a-z0-9-]+"</b>. I hope this is clear to you know as it's similar to what we created in point 1 above, we just added all acceptable characters in square brackets followed by a +. The regex combining everything we discussed till now is: <b>"[A-Za-z0-9._-]+@[a-z0-9-]+"</b><br /><br />3. Now, as per point 3, we should have a dot <b>"."</b> followed by two or more characters in the range of lowercase alphabets a to z. So, the regex for this will be: <b>"\.[a-z]{2,}$"</b>. As you can see first of all we have a dot escaped by "\" character as: <b>"\."</b> . This is because dot is a reserved character in regex which means "any character". After dot, we have lowercase a to z in square brackets and then we have curly brackets as <b>{2,}</b> which means we should have two or more occurences of the characters specified in square brackets. So, our regex is complete and each <b>regex always ends with a</b> <b>$</b> to specify the end, therefore we have added a $ at the end.</div><div><br /></div><div>The final regex for our email validation is: <b>[A-Za-z0-9._-]+@[a-z0-9-]+\.[a-z]{2,}$</b></div><div><b><br /></b></div><div>We can specify a regex by using the pattern property of the <b>lightning-input</b>, so our updated email field will be:</div><pre><lightning-input name="email" class="validate" type="email" label="Email" pattern="[A-Za-z0-9._-]+@[a-z0-9-]+\.[a-z]{2,}$" message-when-pattern-mismatch="Please enter a valid email" required></lightning-input></pre><div>As you can see above, I have added my regex in the pattern field and have added another property named as <b>message-when-pattern-mismatch</b> with a value: <b>"Please enter a valid email"</b>. The <b>message-when-pattern-mismatch</b> property is used to specify an error message when the input entered by user is not matching the pattern specified in the <b>pattern</b> property.</div><div><br /></div><div>Our email field is fixed now, let's add a validation for our phone field as well:</div><h3 style="text-align: left;">Setting up validation for Phone field</h3><div>For phone field, a simple phone number can be in the format: <b>123-456-7890</b>. That means, we can have 3 numbers followed by a hyphen <b>"-"</b>, followed by another 3 numbers and then again a hyphen <b>"-"</b> followed by 4 numbers at the end.</div><div><br /></div><div>Therefore, the regex will be: <b>[0-9]{3}-[0-9]{3}-[0-9]{4}$</b></div><div><b><br /></b></div><div>Here, <b>[0-9]{3}</b> represents: we're allowing 3 characters in the range 0-9. Similarly for 4 numbers we have specified <b>[0-9]{4}</b>.</div><div><br /></div><div>For the error message, we're going to specify <b>Please enter a valid phone number</b> as a value for the <b>message-when-pattern-mismatch</b> property. The updated phone field will be:</div><pre><lightning-input name="phone" class="validate" type="tel" label="Phone" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}$" message-when-pattern-mismatch="Please enter a valid phone number" required></lightning-input></pre><div>Our phone field is also fixed now, let's add a validation for pin code as well.</div><h3 style="text-align: left;">Setting up validation for Pin Code field</h3><div>For Pin Code field, we're going to setup a minimum and maximum value for pin code and set the error messages accordingly. lightning-input has min and max property so let's say we can have any value starting with a 4 digit number upto 6 digit number in the pin code. So, we're going to set <b>min</b> as <b>1000</b> and <b>max</b> as <b>999999</b>. We also have other properties to setup if the user enters a value outside the range. <b>message-when-range-overflow</b> property is used to specify an error message which will be displayed when a value is greater than the value specified in max. Similarly, <b>message-when-range-underflow</b> property is used to specify an error message which will be displayed when a value is less than the value specified in min.</div><div><br /></div><div>So, the updated pin code field will be:</div><pre><lightning-input name="pincode" class="validate" type="number" label="Pin Code" min="1000" max="999999" message-when-range-overflow="Please enter a correct pincode" message-when-range-underflow="Please enter a correct pincode"></lightning-input></pre><div><br /></div><div>Let's have a look at our updated form as a whole:</div><pre><template>
<lightning-card title="Sign Up">
<p class="slds-var-p-around_small">
<lightning-layout multiple-rows>
<lightning-layout-item padding="around-small" size="12" medium-device-size="6" large-device-size="6">
<lightning-input name="firstName" class="validate" type="text" label="First Name" required></lightning-input>
<lightning-input name="password" class="validate" type="password" label="Password" required></lightning-input>
<lightning-input name="email" class="validate" type="email" label="Email" pattern="[A-Za-z0-9._-]+@[a-z0-9-]+.[a-z]{2,}$" message-when-pattern-mismatch="Please enter a valid email" required></lightning-input>
<lightning-combobox name="country" class="validate" label="Country" options={countryOptions} required></lightning-combobox>
</lightning-layout-item>
<lightning-layout-item padding="around-small" size="12" medium-device-size="6" large-device-size="6">
<lightning-input name="lastName" class="validate" type="text" label="Last Name" required></lightning-input>
<lightning-input name="company" class="validate" type="text" label="Company" required></lightning-input>
<lightning-input name="phone" class="validate" type="tel" label="Phone" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}$" message-when-pattern-mismatch="Please enter a valid phone number" required></lightning-input>
<lightning-input name="pincode" class="validate" type="number" label="Pin Code" min="1000" max="999999" message-when-range-overflow="Please enter a correct pincode" message-when-range-underflow="Please enter a correct pincode"></lightning-input>
</lightning-layout-item>
<lightning-layout-item size="12" class="slds-var-p-top_small">
<lightning-button class="slds-align_absolute-center" label="Sign Up" onclick={createContact}></lightning-button>
</lightning-layout-item>
</lightning-layout>
</p>
</lightning-card>
</template></pre><h3 style="text-align: left;">JavaScript Code</h3><div>I hope you understood how we've implemented regex in order to validate the fields, now it's time to add some JavaScript code to validate all these fields on button click. Let's have a look at the below JavaScript code:</div><pre>import { LightningElement } from 'lwc';
export default class ContactForm extends LightningElement {
contact = {};
countryOptions = [
{ "label": "Afghanistan", "value": "Afghanistan" },
{ "label": "Albania", "value": "Albania" },
{ "label": "Algeria", "value": "Algeria" },
{ "label": "Andorra", "value": "Andorra" },
{ "label": "Angola", "value": "Angola" },
{ "label": "Antigua & Deps", "value": "Antigua & Deps"},
{ "label": "Argentina", "value": "Argentina" },
{ "label": "Armenia", "value": "Armenia" },
{ "label": "Australia", "value": "Australia" },
{ "label": "Austria", "value": "Austria" },
{ "label": "Azerbaijan", "value": "Azerbaijan" },
{ "label": "Bahamas", "value": "Bahamas" },
{ "label": "Bahrain", "value": "Bahrain" },
{ "label": "Bangladesh", "value": "Bangladesh" },
{ "label": "Barbados", "value": "Barbados" },
{ "label": "Belarus", "value": "Belarus" },
{ "label": "Belgium", "value": "Belgium" },
{ "label": "Belize", "value": "Belize" },
{ "label": "Benin", "value": "Benin" },
{ "label": "Bhutan", "value": "Bhutan" },
{ "label": "Bolivia", "value": "Bolivia" },
{ "label": "Bosnia Herzegovina", "value": "Bosnia Herzegovina"},
{ "label": "Botswana", "value": "Botswana" },
{ "label": "Brazil", "value": "Brazil" },
{ "label": "Brunei", "value": "Brunei" },
{ "label": "Bulgaria", "value": "Bulgaria" },
{ "label": "Burkina", "value": "Burkina" },
{ "label": "Burundi", "value": "Burundi" },
{ "label": "Cambodia", "value": "Cambodia" },
{ "label": "Cameroon", "value": "Cameroon" },
{ "label": "Canada", "value": "Canada" },
{ "label": "Cape Verde", "value": "Cape Verde"},
{ "label": "Central African Rep", "value": "Central African Rep"},
{ "label": "Chad", "value": "Chad" },
{ "label": "Chile", "value": "Chile" },
{ "label": "China", "value": "China" },
{ "label": "Colombia", "value": "Colombia" },
{ "label": "Comoros", "value": "Comoros" },
{ "label": "Congo", "value": "Congo" },
{ "label": "Congo {Democratic Rep}", "value": "Congo {Democratic Rep}"},
{ "label": "Costa Rica", "value": "Costa Rica"},
{ "label": "Croatia", "value": "Croatia" },
{ "label": "Cuba", "value": "Cuba" },
{ "label": "Cyprus", "value": "Cyprus" },
{ "label": "Czech Republic", "value": "Czech Republic"},
{ "label": "Denmark", "value": "Denmark" },
{ "label": "Djibouti", "value": "Djibouti" },
{ "label": "Dominica", "value": "Dominica" },
{ "label": "Dominican Republic", "value": "Dominican Republic"},
{ "label": "East Timor", "value": "East Timor"},
{ "label": "Ecuador", "value": "Ecuador" },
{ "label": "Egypt", "value": "Egypt" },
{ "label": "El Salvador", "value": "El Salvador"},
{ "label": "Equatorial Guinea", "value": "Equatorial Guinea"},
{ "label": "Eritrea", "value": "Eritrea" },
{ "label": "Estonia", "value": "Estonia" },
{ "label": "Ethiopia", "value": "Ethiopia" },
{ "label": "Fiji", "value": "Fiji" },
{ "label": "Finland", "value": "Finland" },
{ "label": "France", "value": "France" },
{ "label": "Gabon", "value": "Gabon" },
{ "label": "Gambia", "value": "Gambia" },
{ "label": "Georgia", "value": "Georgia" },
{ "label": "Germany", "value": "Germany" },
{ "label": "Ghana", "value": "Ghana" },
{ "label": "Greece", "value": "Greece" },
{ "label": "Grenada", "value": "Grenada" },
{ "label": "Guatemala", "value": "Guatemala" },
{ "label": "Guinea", "value": "Guinea" },
{ "label": "Guinea-Bissau", "value": "Guinea- Bissau"},
{ "label": "Guyana", "value": "Guyana" },
{ "label": "Haiti", "value": "Haiti" },
{ "label": "Honduras", "value": "Honduras" },
{ "label": "Hungary", "value": "Hungary" },
{ "label": "Iceland", "value": "Iceland" },
{ "label": "India", "value": "India" },
{ "label": "Indonesia", "value": "Indonesia" },
{ "label": "Iran", "value": "Iran" },
{ "label": "Iraq", "value": "Iraq" },
{ "label": "Ireland {Republic}", "value": "Ireland {Republic}"},
{ "label": "Israel", "value": "Israel" },
{ "label": "Italy", "value": "Italy" },
{ "label": "Ivory Coast", "value": "Ivory Coast"},
{ "label": "Jamaica", "value": "Jamaica" },
{ "label": "Japan", "value": "Japan" },
{ "label": "Jordan", "value": "Jordan" },
{ "label": "Kazakhstan", "value": "Kazakhstan" },
{ "label": "Kenya", "value": "Kenya" },
{ "label": "Kiribati", "value": "Kiribati" },
{ "label": "Korea North", "value": "Korea North"},
{ "label": "Korea South", "value": "Korea South"},
{ "label": "Kosovo", "value": "Kosovo" },
{ "label": "Kuwait", "value": "Kuwait" },
{ "label": "Kyrgyzstan", "value": "Kyrgyzstan" },
{ "label": "Laos", "value": "Laos" },
{ "label": "Latvia", "value": "Latvia" },
{ "label": "Lebanon", "value": "Lebanon" },
{ "label": "Lesotho", "value": "Lesotho" },
{ "label": "Liberia", "value": "Liberia" },
{ "label": "Libya", "value": "Libya" },
{ "label": "Liechtenstein", "value": "Liechtenstein" },
{ "label": "Lithuania", "value": "Lithuania" },
{ "label": "Luxembourg", "value": "Luxembourg" },
{ "label": "Macedonia", "value": "Macedonia" },
{ "label": "Madagascar", "value": "Madagascar" },
{ "label": "Malawi", "value": "Malawi" },
{ "label": "Malaysia", "value": "Malaysia" },
{ "label": "Maldives", "value": "Maldives" },
{ "label": "Mali", "value": "Mali" },
{ "label": "Malta", "value": "Malta" },
{ "label": "Marshall Islands", "value": "Marshall Islands"},
{ "label": "Mauritania", "value": "Mauritania" },
{ "label": "Mauritius", "value": "Mauritius" },
{ "label": "Mexico", "value": "Mexico" },
{ "label": "Micronesia", "value": "Micronesia" },
{ "label": "Moldova", "value": "Moldova" },
{ "label": "Monaco", "value": "Monaco" },
{ "label": "Mongolia", "value": "Mongolia" },
{ "label": "Montenegro", "value": "Montenegro" },
{ "label": "Morocco", "value": "Morocco" },
{ "label": "Mozambique", "value": "Mozambique" },
{ "label": "Myanmar, {Burma}", "value": "Myanmar, {Burma}"},
{ "label": "Namibia", "value": "Namibia" },
{ "label": "Nauru", "value": "Nauru" },
{ "label": "Nepal", "value": "Nepal" },
{ "label": "Netherlands", "value": "Netherlands" },
{ "label": "New Zealand", "value": "New Zealand"},
{ "label": "Nicaragua", "value": "Nicaragua" },
{ "label": "Niger", "value": "Niger" },
{ "label": "Nigeria", "value": "Nigeria" },
{ "label": "Norway", "value": "Norway" },
{ "label": "Oman", "value": "Oman" },
{ "label": "Pakistan", "value": "Pakistan" },
{ "label": "Palau", "value": "Palau" },
{ "label": "Panama", "value": "Panama" },
{ "label": "Papua New Guinea", "value": "Papua New Guinea"},
{ "label": "Paraguay", "value": "Paraguay" },
{ "label": "Peru", "value": "Peru" },
{ "label": "Philippines", "value": "Philippines" },
{ "label": "Poland", "value": "Poland" },
{ "label": "Portugal", "value": "Portugal" },
{ "label": "Qatar", "value": "Qatar" },
{ "label": "Romania", "value": "Romania" },
{ "label": "Russian Federation", "value": "Russian Federation"},
{ "label": "Rwanda", "value": "Rwanda" },
{ "label": "St Kitts & Nevis", "value": "St Kitts & Nevis"},
{ "label": "St Lucia", "value": "St Lucia"},
{ "label": "Saint Vincent & the Grenadines", "value": "Saint Vincent & the Grenadines"},
{ "label": "Samoa", "value": "Samoa" },
{ "label": "San Marino", "value": "San Marino"},
{ "label": "Sao Tome & Principe", "value": "Sao Tome & Principe"},
{ "label": "Saudi Arabia", "value": "Saudi Arabia"},
{ "label": "Senegal", "value": "Senegal" },
{ "label": "Serbia", "value": "Serbia" },
{ "label": "Seychelles", "value": "Seychelles" },
{ "label": "Sierra Leone", "value": "Sierra Leone"},
{ "label": "Singapore", "value": "Singapore" },
{ "label": "Slovakia", "value": "Slovakia" },
{ "label": "Slovenia", "value": "Slovenia" },
{ "label": "Solomon Islands", "value": "Solomon Islands"},
{ "label": "Somalia", "value": "Somalia" },
{ "label": "South Africa", "value": "South Africa"},
{ "label": "South Sudan", "value": "South Sudan"},
{ "label": "Spain", "value": "Spain" },
{ "label": "Sri Lanka", "value": "Sri Lanka"},
{ "label": "Sudan", "value": "Sudan" },
{ "label": "Suriname", "value": "Suriname" },
{ "label": "Swaziland", "value": "Swaziland" },
{ "label": "Sweden", "value": "Sweden" },
{ "label": "Switzerland", "value": "Switzerland" },
{ "label": "Syria", "value": "Syria" },
{ "label": "Taiwan", "value": "Taiwan" },
{ "label": "Tajikistan", "value": "Tajikistan" },
{ "label": "Tanzania", "value": "Tanzania" },
{ "label": "Thailand", "value": "Thailand" },
{ "label": "Togo", "value": "Togo" },
{ "label": "Tonga", "value": "Tonga" },
{ "label": "Trinidad & Tobago", "value": "Trinidad & Tobago"},
{ "label": "Tunisia", "value": "Tunisia" },
{ "label": "Turkey", "value": "Turkey" },
{ "label": "Turkmenistan", "value": "Turkmenistan" },
{ "label": "Tuvalu", "value": "Tuvalu" },
{ "label": "Uganda", "value": "Uganda" },
{ "label": "Ukraine", "value": "Ukraine" },
{ "label": "United Arab Emirates", "value": "United Arab Emirates"},
{ "label": "United Kingdom", "value": "United Kingdom"},
{ "label": "United States", "value": "United States"},
{ "label": "Uruguay", "value": "Uruguay" },
{ "label": "Uzbekistan", "value": "Uzbekistan" },
{ "label": "Vanuatu", "value": "Vanuatu" },
{ "label": "Vatican City", "value": "Vatican City"},
{ "label": "Venezuela", "value": "Venezuela" },
{ "label": "Vietnam", "value": "Vietnam" },
{ "label": "Yemen", "value": "Yemen" },
{ "label": "Zambia", "value": "Zambia" },
{ "label": "Zimbabwe", "value": "Zimbabwe" }
];
/*
* This method is used to check if all the input fields
* that we need to validate are valid or not. We're also going
* to populate our contact object so that it can be sent to apex
* in order to save the details in salesforce
*/
isInputValid() {
let isValid = true;
let inputFields = this.template.querySelectorAll('.validate');
inputFields.forEach(inputField => {
if(!inputField.checkValidity()) {
inputField.reportValidity();
isValid = false;
}
this.contact[inputField.name] = inputField.value;
});
return isValid;
}
/*
* This method is used to create a new contact in salesforce
* based on the values entered by the user. For now, our main
* purpose is validation so, we're just going to display the
* contact object once it's validated to make sure that we
* have all the fields and their values ready to be saved
*/
createContact() {
if(this.isInputValid()) {
console.log(this.contact);
}
}
}</pre><div>In the above code snippet, <b>contact</b> is just used to store the data that's entered by user. <b>countryOptions</b> is storing a list of countries with their label and values as these countries are displayed in <b>lightning-combobox</b>. After that we have two methods: <b>isInputValid()</b> and <b>createContact()</b>.</div><h3 style="text-align: left;">isInputValid()</h3><div>This method is used to validate all the fields to make sure that all the values entered are correct or not. First of all we have a variable inside this method named as <b>isValid</b> which is <b>true</b> by default. Then we're getting all the input fields using <b>this.template.querySelectorAll('.validate');</b> if you remember, we've given the same class name <b>validate</b> to all the input fields that we want to validate. We're using the same <b>validate</b> class here to get references of all input fields together in order to form <b>inputFields</b> array.</div><div><br /></div><div>Then, we're iterating this <b>inputFields</b> array using a <b>forEach</b> loop and for each <b>inputField</b>, we're first of all checking the validity using <b>checkValidity()</b> inside an if condition. If the input field is invalid that means, we have a validation error. We're reporting that validity by calling <b>reportValidity()</b> method on inputField as: <b>inputField.reportValidity();</b> . This method will mainly show the error message on the field. Then, we're setting up <b>isValid</b> to <b>false</b> as the inputField is not valid. Outside the if condition, we're populating our <b>contact</b> object by using <b>inputField.name</b> as the key and <b>inputField.value</b> as the value. If you check the html, we've populated the name attribute for each inputField that we're using here. Finally, once the forEach loop is completed, we're returning the <b>isValid</b> variable (which is a boolean) from the <b>isInputValid()</b> method.</div><h3 style="text-align: left;">createContact()</h3><div>As the sign up button is clicked in html, the <b>createContact()</b> method is called, Inside this method, first of all we're calling the <b>isInputValid()</b> method inside the if condition to confirm if the values entered in the form are valid or not. If the inputs are valid, we're simply displaying the <b>contact</b> object in the js console to verify the values.</div><div><br /></div><h3 style="text-align: left;">Validation Demo</h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNzXjQwJr657YG0tFLbsxyw0MFYLEYpkivMEAEn_CL8hvcyFqto8ZImOw_yeQxODvvnQVFQOItE4ImlLtid1BY323Inodn0eXFhOfsFXcmMqiQdCiaJgzDAJ_RKeIt_bqEjKbsa5y1P48/s555/lwc+validation.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="472" data-original-width="555" height="544" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNzXjQwJr657YG0tFLbsxyw0MFYLEYpkivMEAEn_CL8hvcyFqto8ZImOw_yeQxODvvnQVFQOItE4ImlLtid1BY323Inodn0eXFhOfsFXcmMqiQdCiaJgzDAJ_RKeIt_bqEjKbsa5y1P48/w640-h544/lwc+validation.gif" width="640" /></a></div><div><br /></div><div>If we have filled all the field values properly, we'll see the contact object in the console as shown below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil91ML6mHK0ejxyo1qEgKFXplF3_xH2E9uo6n_DbY8g14x8hyphenhyphene0VuQk2qxPTBgR3Y4cSXRJ4_EVkBsILuCku41OTsPUcHIpgovX8o7w1o5ImEWpcmxDckkVfc5Mk3ivRYj8eG6QmsmSRs/s1272/Screenshot+2021-07-31+at+1.44.30+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="1272" height="242" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil91ML6mHK0ejxyo1qEgKFXplF3_xH2E9uo6n_DbY8g14x8hyphenhyphene0VuQk2qxPTBgR3Y4cSXRJ4_EVkBsILuCku41OTsPUcHIpgovX8o7w1o5ImEWpcmxDckkVfc5Mk3ivRYj8eG6QmsmSRs/w640-h242/Screenshot+2021-07-31+at+1.44.30+PM.png" width="640" /></a></div><br /><div>That's all for this tutorial everyone, I hope you liked it and understood, how you can setup custom validation in lwc. Let me know if you have any questions or feedback in the comments down below. The full code for this tutorial can be found at the GitHub repository <a href="https://github.com/rahulmalhotra/salesforce-lwc-concepts/tree/validation/force-app/main/default/lwc/contactForm" target="_blank"><span style="color: #2b00fe;">here</span></a>.</div><div><br /></div><div><b>Happy Trailblazing..!!</b></div><div><br /></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-16851771180768454712021-08-23T22:30:00.096-07:002021-09-07T01:10:36.162-07:00Understanding Dynamic Apex and it's Use Cases<p>Hello Trailblazers,</p><p>In this post we're going to learn about Dynamic Apex and the most common use cases that we can solve using it. Let's begin.</p><h3>Tutorial Video</h3><p></p><h3></h3><p></p><p style="-webkit-text-stroke-width: 0px;"><iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="400" src="https://www.youtube.com/embed/g-3imAG7lcc" width="100%"></iframe><br /></p><h2 style="text-align: left;">What is Dynamic Apex?</h2><div>As per the <a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dynamic.htm" target="_blank"><span style="color: #2b00fe;">Salesforce Documentation</span></a>, Dynamic Apex enables developers to create more flexible applications. In other words, we can say that using dynamic apex, you can write <b>Generic Code</b> that can be re-used again and again with multiple sObjects. So, the logic that you've written is not object dependent. You can make your SOQL queries, SOSL queries and DML statements dynamic. Let's have a look at some good use cases to see how that works.</div><div><br /></div><h2 style="text-align: left;">Get all object API names with their labels</h2><div>Sometimes, you need to get a list of all sObjects that are present in your org. Let's have a look at the <a href="https://www.sfdcstop.com/2018/03/sobject-convertor-utility-application.html" target="_blank"><span style="color: #2b00fe;">sObject convertor</span></a> screenshot to understand the use case:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVij_DsdzCx6zj5zpanV0dOG1JwqMr-laNftVWsx18ulAeNrLLkkiHvwEX-OWylxYSWFYpguMNC2T7p6uQUpHyjIqYqFiVN9opCQCis2wXsVm1NpasaQOfSp9KK_LQyu2maBtFrlmIKQA/s1918/Screenshot+2021-08-07+at+12.48.33+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="368" data-original-width="1918" height="122" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVij_DsdzCx6zj5zpanV0dOG1JwqMr-laNftVWsx18ulAeNrLLkkiHvwEX-OWylxYSWFYpguMNC2T7p6uQUpHyjIqYqFiVN9opCQCis2wXsVm1NpasaQOfSp9KK_LQyu2maBtFrlmIKQA/w640-h122/Screenshot+2021-08-07+at+12.48.33+PM.png" width="640" /></a></div><br /><div>As you can see, here we need to select the Source and Destination sObject to convert the records of source sObject to destination sObject. Therefore, we need to display the API names or Labels of all the sObjects that are present in the salesforce org. Let's see how we can do that using dynamic apex:</div><pre>// * Initializing Map
Map<String, String> objectAPINameToLabelMap = new Map<String, String>();
// * Getting all objects metadata
Map<String, Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe();
// * Processing each sObject one by one
for(String globalDescribeKey : globalDescribeMap.keySet()) {
// * Getting the current sObject type
Schema.SObjectType currentSObjectType = globalDescribeMap.get(globalDescribeKey);
// * Getting the current sObject description result from sObject Type
Schema.DescribeSObjectResult currentSObjectResult = currentSObjectType.getDescribe();
// * Getting the API name and value of current sObject and adding it to the map
objectAPINameToLabelMap.put(currentSObjectResult.getName(), currentSObjectResult.getLabel());
}
// * Processing each entry of the map one by one and displaying sObject API Name and Label
for(String objectAPIName : objectAPINameToLabelMap.keySet()) {
System.debug(objectAPIName + ' --> ' + objectAPINameToLabelMap.get(objectAPIName));
}</pre><div>As you can see in the above code snippet, first of all we're initializing a map of string,string named <b>objectAPINameToLabelMap</b>. Then, we're getting all sObjects metadata from salesforce using <b>getGlobalDescribe()</b> method of <b>Schema</b> class. This method will return a map of sObject name as key and an object of SObjectType class as value. The <b>SObjectType</b> class is defined in <b>Schema</b> namespace.</div><div><br /></div><div>Once, we've a map of object names with their sObject types, we loop each entry in the map and assign each sObject type to the <b>currentSObjectType</b> variable. From the currentSObjectType, we're using the <b>getDescribe()</b> method to get it's metadata which is an object of <b>DescribeSObjectResult</b> class defined in <b>Schema</b> namespace. DescribeSObjectResult consist of all the metadata for current sObject. Once, we have that, we can get the current sobject api name and label using the <b>getName()</b> and <b>getLabel()</b> methods and store them in <b>objectAPINameToLabelMap</b>.</div><div><br /></div><div><b>Important Note:</b> The <b>Schema</b> in <b>Schema.SObjectType</b> and <b>Schema.DescribeSObjectResult</b> is a <b>namespace</b> whereas, the <b>Schema</b> in <b>Schema.getGlobalDescribe()</b> is a class. Please don't get confused between the two.</div><div><br /></div><div>Finally, we're displaying the key and value of each entry in this map using a for loop. The key is the API name of the object and the value is the object label. The output of this code is given below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Xg5HP8QiD0Plpo9VcGwQadwsUMUFVY4z5LDaYb0yhxLpqLTpu_qv-eghiYDJUKUZLyzgOZ-DcYphrtGwa4bgKjG75NJZosSevZ8wKpdRDmwQmZkroG_NEVdiE7GAqeiZF-yWCiTQu1o/s2004/Screenshot+2021-08-07+at+1.08.16+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="852" data-original-width="2004" height="272" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Xg5HP8QiD0Plpo9VcGwQadwsUMUFVY4z5LDaYb0yhxLpqLTpu_qv-eghiYDJUKUZLyzgOZ-DcYphrtGwa4bgKjG75NJZosSevZ8wKpdRDmwQmZkroG_NEVdiE7GAqeiZF-yWCiTQu1o/w640-h272/Screenshot+2021-08-07+at+1.08.16+PM.png" width="640" /></a></div><br /><div>As you can see in the above output, some of the internal objects may not have a label as well. For example, <b>AppointmentTopicTimeSlotFeed</b> is not having a label and we're getting the text: "__MISSING LABEL__ PropertyFile - val AppointmentTopicTimeSlot not found in section StandardFeedLabel" from the getLabel() method. It's a high possibility that such objects are internal and you don't need to use these objects in your logic.</div><div><br /></div><h2 style="text-align: left;">Checking object permissions for the logged in user</h2><div>We can also get the sObjectType by using the <b>getSObjectType()</b> method directly on the object api name. Let's consider the below code:</div><pre>Schema.SObjectType currentObjectType = AppointmentTopicTimeSlotFeed.getSObjectType();
Schema.DescribeSObjectResult currentSObjectResult = currentObjectType.getDescribe();
System.debug('API Name = ' + currentSObjectResult.getName());
System.debug('Is Custom Object = ' + currentSObjectResult.isCustom());
System.debug('Is Accessible = ' + currentSObjectResult.isAccessible());
System.debug('Is Creatable = ' + currentSObjectResult.isCreateable());
System.debug('Is Updateable = ' + currentSObjectResult.isUpdateable());
System.debug('Is Deletable = ' + currentSObjectResult.isDeletable());</pre><div>As you can see above, first of all we're getting the SObjectType by using the <b>getSObjectType()</b> method on <b>AppointmentTopicTimeSlotFeed</b> object. Then, we're getting it's DescribeSObjectResult by using the <b>getDescribe()</b> method as we have done before. Then, we're displaying some properties of the object like:</div><div><ul style="text-align: left;"><li>Object API Name</li><li>Is the object a custom object?</li><li>Is the object accessible?</li><li>Is the object creatable?</li><li>Is the object updateable?</li><li>Is the object deletable?</li></ul><div><b>The answer to these questions are really necessary when you want to check the object permissions for the current logged in user before perfoming an operation like: SOQL, DML etc.</b> Let's have a look at the output below:</div></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlkIazDVSi44bHCZO7MMHD5I_-xJLohxlmV87eYIonRqiGEF5zr_JS_z81CS9u1P9zxMisuhFssUUZKny0mV0wAx_4zAHNXpWaIDyd2odUEIqBUUADbMRhOpRGjGFqilHFujuvW6nAlfk/s1024/Screenshot+2021-08-07+at+1.22.57+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="1024" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlkIazDVSi44bHCZO7MMHD5I_-xJLohxlmV87eYIonRqiGEF5zr_JS_z81CS9u1P9zxMisuhFssUUZKny0mV0wAx_4zAHNXpWaIDyd2odUEIqBUUADbMRhOpRGjGFqilHFujuvW6nAlfk/w640-h250/Screenshot+2021-08-07+at+1.22.57+PM.png" width="640" /></a></div><br /><div>As you can see, the object is not a custom object, it's records are accessible and deletable by the user, but the user cannot create or update the records. <b>Depending upon these properties, we decide whether to consider this object in our generic code for business logic or not</b>.</div><div><br /></div><h2 style="text-align: left;">Get all the fields for an object</h2><div>Sometimes it's required to get a list of fields for a standard/custom sobject. We used the same in <a href="https://www.sfdcstop.com/2018/03/sobject-convertor-utility-application.html" target="_blank"><span style="color: #2b00fe;">sObject Convertor</span></a> as well because after selecting the objects, we need to define field mapping.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYVMhfV4I1Za9NkknDHDe0bPcfEC9ynXoZS4i2p-7c12DV44DsliIH4mZkuQgi817XE-dYSVxABzcLJsN7It9p4ArIEdF-_WIS1-S2GfvnFRYMYUh1SUv4oQqr25GMISy-5CR1iQCwMrE/s1914/Screenshot+2021-08-08+at+10.11.30+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="750" data-original-width="1914" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYVMhfV4I1Za9NkknDHDe0bPcfEC9ynXoZS4i2p-7c12DV44DsliIH4mZkuQgi817XE-dYSVxABzcLJsN7It9p4ArIEdF-_WIS1-S2GfvnFRYMYUh1SUv4oQqr25GMISy-5CR1iQCwMrE/w640-h250/Screenshot+2021-08-08+at+10.11.30+AM.png" width="640" /></a></div><div>Let's say in our case, we want to get all the fields for Account sObject. The below code snippet will help to get it:</div><pre>// * Initializing set of field names
Set<String> accountFieldNames = new Set<String>();
// * Getting the account sObject Type object
Schema.SObjectType accountObjectType = Account.getSObjectType();
// * Getting the sObject describe result for account object (metadata)
Schema.DescribeSObjectResult accountSObjectResult = accountObjectType.getDescribe();
// * Getting the map of account fields
Map<String, Schema.SObjectField> accountFieldsMap = accountSObjectResult.fields.getMap();
// * Processing each account field one by one
for(String accountFieldKey : accountFieldsMap.keySet()) {
// * Getting the current sobject field
Schema.SObjectField accountField = accountFieldsMap.get(accountFieldKey);
// * Getting the current sobject field description (field metadata) from sobject field
Schema.DescribeFieldResult accountFieldResult = accountField.getDescribe();
// * Extracting the api name of the field from field description and adding it to the account field names set
accountFieldNames.add(accountFieldResult.getName());
}
// * Processing each entry of the account field names one by one and displaying them
for(String accountFieldName : accountFieldNames) {
System.debug(accountFieldName);
}</pre><div>As you can see above, first of all we've initialized a set of string <b>accountFieldNames</b>. Then we're getting sobject type of account and storing it in <b>accountObjectType</b> variable. We're also storing describe result of account in <b>accountSObjectResult</b>. Once, we have the account metadata, it's time to get the field metadata now. For that, we've used <b>accountSObjectResult.fields.getMap()</b> to get the fields map for account. (<b>fields</b> is also defined as a method in DescribeSObjectResult class whose return value is a special data type. It should either be followed by a field API name like: fields.Name or it should be followed by getMap() method). Then, we're iterating the field map using a for loop, getting each field one by one and storing it in <b>accountField</b> variable which is of type <b>Schema.SObjectField</b>. This is similar as we used SObjectType in case of object.</div><div><br /></div><div>Now, we have the field reference, we'll extract the metadata from field by using <b>getDescribe()</b> method on the <b>accountField</b> variable. It will return an instance of <b>Schema.DescribeFieldResult</b> which we're storing in <b>accountFieldResult</b> variable. Now, we can use different methods of <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_fields_describe.htm" target="_blank"><span style="color: #2b00fe;">DescribeFieldResult</span></a> class to get different metadata about the field. Finally, we're retrieving the API name of each field using the <b>getName()</b> method and storing it in <b>accountFieldNames</b> set.</div><div><br /></div><div>At last, we're iterating the set and displaying the field names. The output when this code is executed is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3J3iQYHFzmVbLwv3x5fPSf19Lmiaouh0XD_tF6CPuZhCdmEKEp8zoLWSwwXtNQz0O5S1DipziBxZMnf2G2gf4rypKcVVUmdVDNWq07E0CFZAevVUS8s27GmsiuTREajJbwzmOgFyDmXk/s1124/Screenshot+2021-08-07+at+1.54.58+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1124" data-original-width="982" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3J3iQYHFzmVbLwv3x5fPSf19Lmiaouh0XD_tF6CPuZhCdmEKEp8zoLWSwwXtNQz0O5S1DipziBxZMnf2G2gf4rypKcVVUmdVDNWq07E0CFZAevVUS8s27GmsiuTREajJbwzmOgFyDmXk/w560-h640/Screenshot+2021-08-07+at+1.54.58+PM.png" width="560" /></a></div>If you notice, the above code is specific to account, we can also make it a little more generic by using <b>getGlobalDescribe()</b> method that we learned in the previous section. Have a look at the code below:<pre>/*
* Description: This method is used to return all accessible fields for an sObject
*/
public static Set<String> getSObjectFields(String sObjectName) {
// * Initializing fieldNames set
Set<String> fieldNames = new Set<String>();
// * Getting metadata of all sObjects
Map<String, Schema.SObjectType> sObjectMap = Schema.getGlobalDescribe();
// * Getting the reference to current sObject
Schema.SObjectType sObjectTypeInstance = sObjectMap.get(sObjectName);
if(sObjectTypeInstance!=null) {
// * Getting Fields for current sObject
Map<String, Schema.SObjectField> fieldMap = sObjectTypeInstance.getDescribe().fields.getMap();
// * Checking each field one by one, if it's accessible, adding it's name to fieldNames set
for(Schema.SObjectField field: fieldMap.values()) {
Schema.DescribeFieldResult fieldResult = field.getDescribe();
if(fieldResult.isAccessible()) {
fieldNames.add(fieldResult.getName());
}
}
}
// * Returning the fieldNames set
return fieldNames;
}
Set<String> fieldNames = getSObjectFields('Account');
for(String fieldName : fieldNames) {
System.debug(fieldName);
}</pre><div>In the above code, we've defined a method named <b>getSObjectFields()</b> which will return a set of all field names for a particular sobject whose name is passed in the parameter. Notice, how we've used <b>getGlobalDescribe()</b> to get the <b>sObjectMap</b> and then used the <b>sObjectName</b> variable to get the sObjectType. We've also reduced the code by iterating over <b>fieldMap.values()</b> to get a reference to <b>SObjectField</b> directly instead of getting it from map. We're adding the field name to the fieldNames set only if the current field is <b>accessible</b> so that they can be queried later. The result for account fields will be the same as shown below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqJDwtpKuv8VvUtHvTfdckRMLVghUzJEAOu-zkxBTqYsGd88-76Uk24qPjrVsbpLPt1si1lKsHY3rxrvNQdK6uGND0hNzVnXtey3wlL6s92RVCk7l12mE6HNPX8sAseZVVSDIgoetYums/s1124/Screenshot+2021-08-07+at+1.54.58+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1124" data-original-width="982" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqJDwtpKuv8VvUtHvTfdckRMLVghUzJEAOu-zkxBTqYsGd88-76Uk24qPjrVsbpLPt1si1lKsHY3rxrvNQdK6uGND0hNzVnXtey3wlL6s92RVCk7l12mE6HNPX8sAseZVVSDIgoetYums/w560-h640/Screenshot+2021-08-07+at+1.54.58+PM.png" width="560" /></a></div>We modified the code a little to call this method for <b>Employee__c</b> which is a custom object defined in salesforce org.</div><pre>Set<String> fieldNames = getSObjectFields('Employee__c');
for(String fieldName : fieldNames) {
System.debug(fieldName);
}</pre><div>The result of the modified code is given below:<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihn90yygLer9Tzo8rP2VSRJl0OIc2Y9mzDgBj0rIS_ny_9dH7c91hdGaxmYD0Yan-XW_yH_29ebqNAj_zluJZWnaTH4Ovd-JVJxLraYVk9xrj7GblLaGonjjAD-ACBCMkIZKRsHa0JAwc/s1070/Screenshot+2021-08-07+at+5.07.07+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1070" data-original-width="976" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihn90yygLer9Tzo8rP2VSRJl0OIc2Y9mzDgBj0rIS_ny_9dH7c91hdGaxmYD0Yan-XW_yH_29ebqNAj_zluJZWnaTH4Ovd-JVJxLraYVk9xrj7GblLaGonjjAD-ACBCMkIZKRsHa0JAwc/w584-h640/Screenshot+2021-08-07+at+5.07.07+PM.png" width="584" /></a></div>As you can see, we're getting the fields for Employee__c object now. This is the same employee object that we used in <a href="https://www.sfdcstop.com/2021/08/binary-tree-real-world-problem-find.html" target="_blank"><span style="color: #2b00fe;">Find the Lead Manager Tutorial</span></a>. You have a method now which will return the fields for an object. This method itself can be used to form a dynamic SOQL when you want to query all the fields for an object. Let's see how!</div><div><br /></div><h2 style="text-align: left;">Dynamic SOQL</h2><div>We're going to create a method which will return a SOQL query for any object consisting of all the fields that are accessible. Let's see the code quickly:</div><pre>/*
* Description: This method is used to return SOQL query consisting of
* all fields for an object that are accessible by the current user
*/
String getSOQL(String objectName) {
// * Getting the field names using the object name
Set<String> fieldNames = getSObjectFields(objectName);
// * Forming the SOQL query
String query = 'SELECT ';
for(String fieldName : fieldNames) {
query += fieldName + ', ';
}
// * Removing last , from the SOQL query string
query = query.substring(0, query.lastIndexof(','));
// * Adding the object name to the SOQL
query += ' FROM ' + objectName;
// * Returning the SOQL
return query;
}
// * SOQL query to get all fields for contact
String query = getSOQL('Contact');
System.debug(query);</pre><div>In the above code we have defined a method named <b>getSOQL</b> which will accept the object name in the input and will return the SOQL query string in the output with all accessible fields included in the query. Notice that we're using <b>getSObjectFields()</b> method defined in the previous section to get the object fields as a set of strings. We're then iterating the set to form SOQL query. </div><div><br /></div><div>We tried the same method to form a query for <b>Contact</b> object and the result when this code is executed is given below:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCdivb7fnjCQ4sxuSpNzYlCpaRzPDEPcAFfxtNkSrMs0q0bgYv6tOnFd5hqe-Zmm84FLcFaPpdsyg_i5doUoj6DiqwGodPhBr-xKXahh8QlIGyxU7s9O8b8Y-EGtx4i55GSfHdNS_eczc/s2824/Screenshot+2021-08-07+at+5.31.27+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="816" data-original-width="2824" height="184" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCdivb7fnjCQ4sxuSpNzYlCpaRzPDEPcAFfxtNkSrMs0q0bgYv6tOnFd5hqe-Zmm84FLcFaPpdsyg_i5doUoj6DiqwGodPhBr-xKXahh8QlIGyxU7s9O8b8Y-EGtx4i55GSfHdNS_eczc/w640-h184/Screenshot+2021-08-07+at+5.31.27+PM.png" width="640" /></a></div><div><br /></div>In case, we want to query <b>Account</b> fields instead of <b>Contact</b>, we just need to pass Account in the getSOQL() method as: <b>getSOQL('Account')</b> and we'll get the resultant SOQL. The output for the same is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiej8bq9_ocIH1PinhyphenhyphenIGoMXWO1MCxG0dyW6Fwxnw0RffqXjI2Rh1de7WnUXEEIcZidKL_ctBFas4iafPBMQQiJbyC3TmrPITLzKKYLqV-UvGkUHI4n5c7m2TTeCs1YnOAEIbrB56IGQ7s/s2850/Screenshot+2021-08-07+at+5.34.10+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="756" data-original-width="2850" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiej8bq9_ocIH1PinhyphenhyphenIGoMXWO1MCxG0dyW6Fwxnw0RffqXjI2Rh1de7WnUXEEIcZidKL_ctBFas4iafPBMQQiJbyC3TmrPITLzKKYLqV-UvGkUHI4n5c7m2TTeCs1YnOAEIbrB56IGQ7s/w640-h170/Screenshot+2021-08-07+at+5.34.10+PM.png" width="640" /></a></div><br /><div>See, <b>How easy it is now to form a query for any sObject with all fields</b> by using <b>Dynamic Apex</b>. In case you want to actually query the records, you can do that as well by passing this query in Database.query() method as shown below where we've queried Employee__c records:<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvUONjbtYw34-mAJUF3vOA4N9bQrgd8qWEjr93SMYjZStnweFwAELEPn-ZMSCSzE-tl3xqjJwbtu3Xoy2MrZcs8VUa_nFQxBC12rz1BqpF0wm9cml79zCOsPaHHXvlMPylK76xS6lc6GE/s2511/Screenshot+2021-08-07+at+5.42.10+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1252" data-original-width="2511" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvUONjbtYw34-mAJUF3vOA4N9bQrgd8qWEjr93SMYjZStnweFwAELEPn-ZMSCSzE-tl3xqjJwbtu3Xoy2MrZcs8VUa_nFQxBC12rz1BqpF0wm9cml79zCOsPaHHXvlMPylK76xS6lc6GE/w640-h320/Screenshot+2021-08-07+at+5.42.10+PM.png" width="640" /></a></div>This is an implementation of <b>Dynamic SOQL</b>. As we're creating the SOQL query dynamically by adding all the fields and using that. </div><div><b><br /></b></div><div><b>Note: </b>You can also check for each field access for the current logged in user like: createable, updateable etc. in the same way like we did for the object.</div><div><br /></div><div><h2 style="text-align: left;">Get picklist field values</h2><div>By now, you know how do we get a describe field result using dynamic apex right? The <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_fields_describe.htm" target="_blank"><span style="color: #2b00fe;">DescribeFieldResult</span></a> class is having a method named as: <b>getPicklistValues()</b> with a return type as <b>List<Schema.PicklistEntry></b> which will return the picklist values for the mentioned field. In case the field is not a picklist, this method will give a runtime error. Now the question is - <b>How can we identify if the field is picklist or not?</b></div><div><b><br /></b></div><div>We have a method named: <b>getType()</b> in the DescribeFieldResult class which will return an <b>enum</b> specifying the field type. Using this, we can check if the field is a picklist or not. Let's have a look at the below code snippet where I have defined a method which will return the picklist values of a field, given the field name and object API name as parameters:</div><pre>/*
* Description: This method is going to return the picklist field values and the associated label
* for an object and a field which are passed in as parameters
*/
Map<String, String> getPicklistValuesMap(String objectAPIName, String fieldAPIName) {
// * Initializing picklist field map to story value and label of picklist entries
Map<String, String> picklistFieldMap = new Map<String, String>();
// * Getting the field result for the current field
Schema.DescribeFieldResult fieldResult = Schema.getGlobalDescribe().get(objectAPIName).getDescribe().fields.getMap().get(fieldAPIName).getDescribe();
// * Checking if the field type is a picklist
if(fieldResult.getType() == Schema.DisplayType.Picklist) {
// * Getting all picklist entries
List<Schema.PicklistEntry> picklistEntries = fieldResult.getPicklistValues();
// * Looping over all picklist entries one by one
for(Schema.PicklistEntry picklistEntry : picklistEntries) {
// * If the picklist entry is active, getting the label and value and putting those in map
if(picklistEntry.isActive()) {
String picklistLabel = picklistEntry.getLabel();
String picklistValue = picklistEntry.getValue();
picklistFieldMap.put(picklistValue, picklistLabel);
}
}
}
// * Returning the picklist field map
return picklistFieldMap;
}
System.debug('Picklist values for Account Type:');
Map<String, String> picklistValuesMap = getPicklistValuesMap('Account', 'Type');
for(String picklistValue : picklistValuesMap.keySet()) {
System.debug('Label = ' + picklistValuesMap.get(picklistValue) + ', Value = ' + picklistValue);
}</pre><div>We have defined a method named: <b>getPicklistValuesMap()</b> above which is going to accept two parameters: <b>objectAPIName</b> and <b>fieldAPIName</b>. This method will return the <b>picklistFieldMap</b> where we'll have picklist value as the key and picklist label as the value in the map. Inside the method, we're getting the describe field result for the current field using the methods discussed before and storing it in <b>fieldResult</b> variable. Once we have it, we're going to check the type of field result to ensure the current field is a picklist by using the <b>getType()</b> method which will return a <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_enum_Schema_DisplayType.htm" target="_blank"><span style="color: #2b00fe;">DisplayType</span></a> enum value.</div><div><br /></div><div>If the current field is a picklist, we're getting the values of it using <b>getPicklistValues()</b> which will return a <b>List<Schema.PicklistEntry></b>. Each <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Schema_PicklistEntry.htm" target="_blank"><span style="color: #2b00fe;">PicklistEntry</span></a> will have properties like: label, value, isActive, isDefault. We're only going to store label and value of picklist entries which are active, inside the map. Finally, we're going to return the map at the end. Note that the picklist value is the key inside the map and the picklist label is the value inside the map. You can reverse it as well depending upon the use case.</div><div><br /></div><div>In order to test our method, we've tried to get the <b>Type</b> picklist values from the <b>Account</b> object and displayed those by iterating over the map using a loop. The output for the same is given below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQjoAoOEFqwJ7wDShyphenhyphenoKlxQWtmNnwXnDX6CsG59c1JUu2Nrh5MjuduGCWUbtFFyNTOxaJfn11QQ4I6qK51W1Zphhk2S6MX8kdP07Skac40b0MNzngqzLpw7P5-6cp0SBufehojwNRMu0Y/s1646/Screenshot+2021-08-08+at+6.55.43+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="502" data-original-width="1646" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQjoAoOEFqwJ7wDShyphenhyphenoKlxQWtmNnwXnDX6CsG59c1JUu2Nrh5MjuduGCWUbtFFyNTOxaJfn11QQ4I6qK51W1Zphhk2S6MX8kdP07Skac40b0MNzngqzLpw7P5-6cp0SBufehojwNRMu0Y/w640-h196/Screenshot+2021-08-08+at+6.55.43+AM.png" width="640" /></a></div>We're able to get all picklist values and their label by just passing the object api name and the field api name as a parameter to the method, interesting right? All thanks to Dynamic Apex.</div><div><br /></div><h2 style="text-align: left;">Getting the Record Type Id by using Record Type Name</h2><div>We created 3 account record types in salesforce org as shown below:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirPGjZk8lRFC2fWEjVwcuFhhS2XGm3JtJrKFcTR7FH7gW3o8Tm0e95n8LYrtdukml5l-IDw-LPrai9ZxccJVLIRLVjl94xnBy4sPgS3Rr9jDXKxH-csYc_5JSDJm-5TO0r13m0wnkxuck/s2414/Screenshot+2021-08-08+at+7.02.56+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="566" data-original-width="2414" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirPGjZk8lRFC2fWEjVwcuFhhS2XGm3JtJrKFcTR7FH7gW3o8Tm0e95n8LYrtdukml5l-IDw-LPrai9ZxccJVLIRLVjl94xnBy4sPgS3Rr9jDXKxH-csYc_5JSDJm-5TO0r13m0wnkxuck/w640-h150/Screenshot+2021-08-08+at+7.02.56+AM.png" width="640" /></a></div><div>Now, we're going to create a method which will provide us the record type names and their ids in a map. Let's have a look at the code below:</div><pre>/*
* Description: This method is going to return a map of record type name with it's id
*/
Map<String, Id> getRecordTypeIdsByName(String objectAPIName) {
// * Initializing map
Map<String, Id> recordTypesMap = new Map<String, Id>();
// * Getting the object result
Schema.DescribeSObjectResult objectResult = Schema.getGlobalDescribe().get(objectAPIName)?.getDescribe();
if(objectResult!=null) {
// * Getting the record type infos list
List<Schema.RecordTypeInfo> recordTypeInfos = objectResult.getRecordTypeInfos();
// * Processing each record type one by one
for(Schema.RecordTypeInfo recordTypeInfo : recordTypeInfos) {
// * If the current record type is active and avaialable to the logged in user, adding it's name and id to the map
if(recordTypeInfo.isActive() && recordTypeInfo.isAvailable()) {
recordTypesMap.put(recordTypeInfo.getName(), recordTypeInfo.getRecordTypeId());
}
}
}
// * Returning the record types map
return recordTypesMap;
}
// * Displaying reocrd type map for account object
System.debug(getRecordTypeIdsByName('Account'));</pre><div>As you can see above, we have defined a method named <b>getRecordTypeIdsByName</b> which will accept <b>objectAPIName</b> in the parameter and will return <b>recordTypesMap</b> that contains the record type names and their ids. Inside the method, we're getting the describe object result using the object api name, if that's found, we're going to get the record types list for that object which will be <b>List<Schema.RecordTypeInfo></b>. In this list, each entry will be an instance of <a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_class_Schema_RecordTypeInfo.htm" target="_blank"><span style="color: #2b00fe;">RecordTypeInfo</span></a> class. The RecordTypeInfo class has various methods that can be used to get the information about the current record type like: getDeveloperName(), getName(), isActive(), isAvailable() etc. For each recordTypeInfo record, we're first of all checking if it's <b>Active</b> and <b>Available</b> to the current logged in user. If yes, then we're adding that record type name with respect to it's id in the recordTypesMap.</div><div><br /></div><div>We're testing this method by getting all record types for account object. Let's see the output below:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyGTzHydlaBS8_9_I_dz3sBAy1C-8q1zrCkZS2IUZkc-sV-wrMie8NA_7Vw1dW84XJkLwevRMSu7LZ3qFbFt8VYSR6gHFIaDQySzeJxOHj3FBimyEiNB2XwbnTKpY8SE8hetp1fmhWZ3o/s2056/Screenshot+2021-08-08+at+7.32.03+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="242" data-original-width="2056" height="76" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyGTzHydlaBS8_9_I_dz3sBAy1C-8q1zrCkZS2IUZkc-sV-wrMie8NA_7Vw1dW84XJkLwevRMSu7LZ3qFbFt8VYSR6gHFIaDQySzeJxOHj3FBimyEiNB2XwbnTKpY8SE8hetp1fmhWZ3o/w640-h76/Screenshot+2021-08-08+at+7.32.03+AM.png" width="640" /></a></div>We're getting the correct output i.e. the label of the record type with it's id.</div><div><b>Note:</b> If you want to use the developer name of the record type, you can use getDeveloperName() method instead of getName().</div><div><br /></div><h2 style="text-align: left;">Dynamic SOSL</h2><div>You can also use Dynamic Apex to create SOSL. The procedure is same like we did for SOQL. Have a look at the below screenshots from <a href="https://www.sfdcstop.com/2018/03/sobject-convertor-utility-application.html" target="_blank"><span style="color: #2b00fe;">sObject Convertor</span></a> where we used SOSL to find the records based on the source object selected in the component and the search text entered by the user.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6XyjL9945EYxZx4cbVRQzNVpwYRWZT8BLmt9Ibf9GZs4ydP8x8UUsNm2ddoLTAMZcftATvObAeNhlRqDtywxDSSCtbRGQSKSohxZbpwIFWJt67ld5hBsyBhx_ZLZsyE54XE0lt2Zbd2g/s1904/Screenshot+2021-08-08+at+10.18.58+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="728" data-original-width="1904" height="244" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6XyjL9945EYxZx4cbVRQzNVpwYRWZT8BLmt9Ibf9GZs4ydP8x8UUsNm2ddoLTAMZcftATvObAeNhlRqDtywxDSSCtbRGQSKSohxZbpwIFWJt67ld5hBsyBhx_ZLZsyE54XE0lt2Zbd2g/w640-h244/Screenshot+2021-08-08+at+10.18.58+AM.png" width="640" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtIKpmdoCXPd8XXX7-_2P39YTlaRnrFouMBJU2Vss2kUkFrZ8DHJL42WTMRnu2eWDb6hOTlsdU82IRSlB8z37E-r66VzNPs_RJOIz7jCw9_ndCo_4yAeSZOVV9ioNCKqdl1XWQWas3F3o/s2116/Screenshot+2021-08-08+at+10.17.15+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="984" data-original-width="2116" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtIKpmdoCXPd8XXX7-_2P39YTlaRnrFouMBJU2Vss2kUkFrZ8DHJL42WTMRnu2eWDb6hOTlsdU82IRSlB8z37E-r66VzNPs_RJOIz7jCw9_ndCo_4yAeSZOVV9ioNCKqdl1XWQWas3F3o/w640-h298/Screenshot+2021-08-08+at+10.17.15+AM.png" width="640" /></a></div><div><h2 style="text-align: left;">Wrapping it Up</h2><div>So, that's all for this tutorial everyone, I hope you liked it. I have created a helper class using all the methods that we discussed in this tutorial. You can access the class <a href="https://gist.github.com/rahulmalhotra/a36c1cba17196c9ee61b07e7d7fc23f9" target="_blank"><span style="color: #2b00fe;">here</span></a> and can use it in your projects rightaway. Let me kow what other use case you solved using Dynamic Apex in the comments down below.</div><div><br /></div><div><b>Happy Trailblazing..!!</b></div><div><br /></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0tag:blogger.com,1999:blog-8643239839712475364.post-25707232990843716682021-08-16T21:06:00.001-07:002021-09-07T01:12:21.229-07:00Call External API from Lightning Web Component | Fetch API in JavaScript<p>Hello Trailblazers,</p><p><br /></p><p>In this post, we're going to learn how we can call an External System API from a Lightning Web Component. We're going to use Fetch API which provides an interface for fetching resources. You can consider it as an advanced version of XMLHttpRequest. This API is more powerful and easy to use. The Fetch Web API provides a global <b>fetch()</b> method which can be used in JavaScript to perform any kind of callout to an external system across the network and get the data.</p><p><br /></p><p>The Promise returned from fetch() method won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, the Promise will resolve normally. The ok property of the response is set to <b>true</b> if the callout is successful and it's set to <b>false</b> if the response isn’t in the range 200–299 and it will only reject on network failure or if anything prevented the request from completing.</p><h3>Tutorial Video</h3><p></p><h3></h3><p></p><p style="-webkit-text-stroke-width: 0px;"><iframe allow="autoplay; encrypted-media" allowfullscreen="" frameborder="0" height="400" src="https://www.youtube.com/embed/bXn3i_9yArk" width="100%"></iframe><br /></p><p><br /></p><p>To learn about fetch(), we're going to create a lwc component to get the details of a user from GitHub as shown below:</p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrbKU4Dl_ZE-MesUjU9pg9d5v8fWdlmqrtJZvmYfdfj6cnHPyFhM-bXBBxAcQuF8CzCAFIqQyZJptz29rj2_e9dKELjl0ePdVtqpVq6t0O-zCXh_SlFvTQ0-lCMlcgUQXarDd2LqKAZK8/s599/lwc+external+api+callout.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="599" data-original-width="460" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrbKU4Dl_ZE-MesUjU9pg9d5v8fWdlmqrtJZvmYfdfj6cnHPyFhM-bXBBxAcQuF8CzCAFIqQyZJptz29rj2_e9dKELjl0ePdVtqpVq6t0O-zCXh_SlFvTQ0-lCMlcgUQXarDd2LqKAZK8/w492-h640/lwc+external+api+callout.gif" width="492" /></a></div><br /><p>Let's have a look at the below code snippets of this component along with the explanation:</p><h3 style="text-align: left;"><b>githubInfo.html</b></h3><pre><template>
<lightning-card title="Show Github Stats">
<div class="slds-var-m-around_large">
<!-- * Input Username -->
<lightning-layout vertical-align="end">
<lightning-layout-item flexibility="grow">
<lightning-input type="search" value={username} onchange={updateUsername} label="Enter Username"></lightning-input>
</lightning-layout-item>
<lightning-layout-item class="slds-var-p-left_small">
<lightning-button label="Search" variant="brand" onclick={getGithubStats}></lightning-button>
</lightning-layout-item>
</lightning-layout>
<br />
<!-- * Display User Details -->
<div if:true={userPopulated}>
<img src={user.image} height="200" width="200" />
<div class="slds-var-p-vertical_xxx-small slds-text-heading_large">{user.name}</div>
<br />
<div class="slds-var-p-vertical_xxx-small slds-text-heading_small"><b>Github Profile:</b><a href={githubURL} target="_blank"> {githubURL}</a></div>
<div class="slds-var-p-vertical_xxx-small slds-text-heading_small"><b>Website:</b><a href={user.blog} target="_blank"> {user.blog}</a></div>
<div class="slds-var-p-vertical_xxx-small slds-text-heading_small"><b>About:</b> {user.about}</div>
<div class="slds-var-p-vertical_xxx-small slds-text-heading_small"><b>Repos:</b> {user.repos}</div>
<div class="slds-var-p-vertical_xxx-small slds-text-heading_small"><b>Gists:</b> {user.gists}</div>
<div class="slds-var-p-vertical_xxx-small slds-text-heading_small"><b>Followers:</b> {user.followers}</div>
</div>
</div>
</lightning-card>
</template></pre><div class="separator" style="clear: both; text-align: left;">The above HTML code is fairly simple as it's only the design and layout. We have an input field of type search where we're accepting the github username. This input field is binded with <b>username</b> variable which we're going to define in js and it's going to call <b>updateUsername()</b> method whenever we're typing something in this input field so that we can update the username accordingly.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">We also have a button here which is going to call <b>getGithubStats()</b> method from js whenever this button is clicked. The getGithubStats() will be used to fetch data from github using the <b>Fetch API</b> and the details will be displayed in the user details section.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">To display the details of user, we've created a <b>user</b> object in js and we're checking if the <b>userPopulated</b> boolean variable is true or not. We're going to define it as a getter in js, which will return true or false depending upon whether the user object has details or not. We're going to display the details about the user by using the user object such as: user.about, user.blog, user.repos etc.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Now, let's have a look at the js code quickly:</div><h3 style="clear: both; text-align: left;"><b>githubInfo.js</b></h3><div class="separator" style="clear: both; text-align: left;"><pre>import { LightningElement } from 'lwc';
// * GitHub API Base URL
const GITHUB_URL = 'https://api.github.com/users/';
export default class GithubInfo extends LightningElement {
username;
user = {};
// * This method will return if the user object is populated or not
get userPopulated() {
return this.user && this.user.id;
}
// * This method will return the github url for the searched user
get githubURL() {
return 'https://www.github.com/' + this.username;
}
// * This method will set the username as the user is typing the text in the input field
updateUsername(event) {
this.username = event.target.value;
}
// * This method is used to call GitHub API using fetch method and get the user details
getGithubStats() {
if(this.username) {
this.user = {};
fetch(GITHUB_URL + this.username)
.then(response => {
console.log(response);
if(response.ok) {
return response.json();
} else {
throw Error(response);
}
})
.then(githubUser => {
this.user = {
id: githubUser.id,
name: githubUser.name,
image: githubUser.avatar_url,
blog: githubUser.blog,
about: githubUser.bio,
repos: githubUser.public_repos,
gists: githubUser.public_gists,
followers: githubUser.followers
};
})
.catch(error => console.log(error))
} else {
alert('Please specify a username');
}
}
}
</pre><div>The above code consist of a <b>GITHUB_URL</b> constant which is basically storing our base URL for GitHub API. Inside the class, we have two data members: <b>username</b> and <b>user</b> as discussed before. We also have a <b>userPopulated()</b> method defined which is a getter and will return true if user record is present with an id, otherwise, it'll return false. Based on this value we'll display/hide the user details section in HTML.</div><div><br /></div><div>After that, we also have a <b>githubURL()</b> getter which is going to form the profile URL of user, based on the username entered. Then we have an <b>updateUsername()</b> method, which is called automatically while updating text in the username input field, it's updating the <b>username</b> data member with the latest value. Finally, we have a <b>getGithubStats()</b> method which is performing the callout in order to get the user details from github on click of a button.</div><div><br /></div><div><br /></div><div><b>getGithubStats()</b> - In this method, first of all we're checking if the username field is populated, then only we'll proceed ahead, otherwise, we're going to throw an error specifying: <b>Please specify a username</b> in the alert. You can also use a toast here, I have just added an alert to keep it simple.</div><div><br /></div><div>If we have the username populated, we're first of all resetting the user object to a blank object in order to clear the previous user response (if any). Then, we're using the <b>fetch()</b> method to hit the GitHub API. The fetch method accept the URL as the first parameter which is constructed by appending username to the base url as: <b>GITHUB_URL + this.username</b>. After that, we've two <b>then()</b> followed by a <b>catch()</b>. The first then() is going to receive a <b>Response</b> object from the Fetch API. We are using it's <b>ok</b> property to check if the response is successful or not. If it's successful, we're returning the response body by converting it into a JSON object using <b>response.json()</b> which will be received by subsequent then(). If we receive an error, we're throwing an instance of <b>Error</b> by passing the <b>response</b> in the constructor. The subsequent then() will store the JSON result in <b>githubUser</b> object which is used to populate the <b>user</b> data member of the class in order to display the user's data.</div><div><br /></div><div>In case of an error, we're simply displaying it using console.log().</div><div><br /></div><div>To give you a reference of the Github API response, I am displaying it below:</div><div><pre>{
"login": "rahulmalhotra",
"id": 16497903,
"node_id": "MDQ6VXNlcjE2NDk3OTAz",
"avatar_url": "https://avatars.githubusercontent.com/u/16497903?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/rahulmalhotra",
"html_url": "https://github.com/rahulmalhotra",
"followers_url": "https://api.github.com/users/rahulmalhotra/followers",
"following_url": "https://api.github.com/users/rahulmalhotra/following{/other_user}",
"gists_url": "https://api.github.com/users/rahulmalhotra/gists{/gist_id}",
"starred_url": "https://api.github.com/users/rahulmalhotra/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/rahulmalhotra/subscriptions",
"organizations_url": "https://api.github.com/users/rahulmalhotra/orgs",
"repos_url": "https://api.github.com/users/rahulmalhotra/repos",
"events_url": "https://api.github.com/users/rahulmalhotra/events{/privacy}",
"received_events_url": "https://api.github.com/users/rahulmalhotra/received_events",
"type": "User",
"site_admin": false,
"name": "Rahul Malhotra",
"company": null,
"blog": "https://rahulmalhotra.github.io/",
"location": null,
"email": null,
"hireable": true,
"bio": "I am a developer and I love to Code. I am an independent Salesforce Consultant. Blogger and YouTuber at SFDC Stop (https://www.sfdcstop.com/)",
"twitter_username": "rahulcoder",
"public_repos": 58,
"public_gists": 101,
"followers": 71,
"following": 2,
"created_at": "2015-12-31T07:03:03Z",
"updated_at": "2021-07-23T11:30:20Z"
}</pre></div><div>As you can see above, we've properties like: name, avatar_url, public_repos, public_gists etc. That's why we've have used the same properties to map it to the properties of user object:</div><div><pre>.then(githubUser => {
this.user = {
id: githubUser.id,
name: githubUser.name,
image: githubUser.avatar_url,
blog: githubUser.blog,
about: githubUser.bio,
repos: githubUser.public_repos,
gists: githubUser.public_gists,
followers: githubUser.followers
};
})
</pre></div><div><br /></div><div>It's time to look at the meta file now:</div><h3 style="text-align: left;"><b>githubInfo.js-meta.xml</b></h3></div><pre><?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle></pre>We've exposed this component by setting up <b>isExposed</b> as true and added a single target named as: <b>lightning__HomePage</b> as we want to embed this component in the home page.<div><h3 style="text-align: left;">Setting up CSP Trusted Sites</h3><div>So, we embedded our component on the homepage and tried to execute this code to get the details from github by entering the username and clicking on <b>Search</b> button.</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQf0hHvXoHCnpjsJEc8WPsk-SFaRbqGadnmvGuQdbyczlR07Am3KBykOosUc1ufAf5mHVi8fPB4rw3XxJaN32TK31hCvhX7R9XaQrjqP0OiVYaF8V4kMhiEXl8Gst-mqUI0IG4xtkw4KI/s1394/Screenshot+2021-07-27+at+11.35.19+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1154" data-original-width="1394" height="331" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQf0hHvXoHCnpjsJEc8WPsk-SFaRbqGadnmvGuQdbyczlR07Am3KBykOosUc1ufAf5mHVi8fPB4rw3XxJaN32TK31hCvhX7R9XaQrjqP0OiVYaF8V4kMhiEXl8Gst-mqUI0IG4xtkw4KI/w400-h331/Screenshot+2021-07-27+at+11.35.19+AM.png" width="400" /></a></div><div><br /></div><div>However, we received the below error in console:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMEAS5jclaJNYGwQB09PS-51mJ3rTyutt3_pPx9_9_eBg1GGXjPK7lcKG6IoLgH7RZoixPjCy_s0rWeIMuzMYhHNMjHXrQZlE5beDq5WHpLp1AQOytJ6IQA_q1cyMLLcVt4P16pO66TJY/s1808/Screenshot+2021-07-27+at+11.08.15+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="106" data-original-width="1808" height="38" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMEAS5jclaJNYGwQB09PS-51mJ3rTyutt3_pPx9_9_eBg1GGXjPK7lcKG6IoLgH7RZoixPjCy_s0rWeIMuzMYhHNMjHXrQZlE5beDq5WHpLp1AQOytJ6IQA_q1cyMLLcVt4P16pO66TJY/w640-h38/Screenshot+2021-07-27+at+11.08.15+AM.png" width="640" /></a></div><br /><div>This error is coming because we haven't notified salesforce that we're going to call this external api and by default salesforce will not allow us to call the external url from lwc. In order to resolve this, we need to tell salesforce that we're going to hit GitHub API from our lightning components. We can do this by creating a record of CSP Trusted Sites. Follow the below steps to add a record of the same:</div><div><br /></div><div>1. Go to setup and search for <b>CSP Trusted Sites</b></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy8XSMbR_Pna5jGhgnyaniSXjHfZ6rr3IawvoMhEa-oq6w9LLhp7xFNuxwnd61aYaMLXan12-toyvoSkSY0YPD8GSlsp532zwYw_rWTRxuywjbARxBcwInb09kbRf1yI1S7ovkBXJwxJ4/s2868/Screenshot+2021-07-27+at+11.10.43+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="726" data-original-width="2868" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy8XSMbR_Pna5jGhgnyaniSXjHfZ6rr3IawvoMhEa-oq6w9LLhp7xFNuxwnd61aYaMLXan12-toyvoSkSY0YPD8GSlsp532zwYw_rWTRxuywjbARxBcwInb09kbRf1yI1S7ovkBXJwxJ4/w640-h162/Screenshot+2021-07-27+at+11.10.43+AM.png" width="640" /></a></div><br /><div>2. Click on <b>New Trusted Site</b> button and fill up the information as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDZ0EGDUlVOjFnAA1a_2gNnPuk-VKEcJupdJdY9wFqre7cheLDXKQn3_xJ5ZlKLsBRD9OSVJq9y0MlsfDeFZ-wHC-JeYUQKOfqmrvvAlDG3GDUW4JVmYTUDWplzLf9BajCw0cSdyXOe4o/s2048/Screenshot+2021-07-27+at+11.13.19+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1355" data-original-width="2048" height="424" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDZ0EGDUlVOjFnAA1a_2gNnPuk-VKEcJupdJdY9wFqre7cheLDXKQn3_xJ5ZlKLsBRD9OSVJq9y0MlsfDeFZ-wHC-JeYUQKOfqmrvvAlDG3GDUW4JVmYTUDWplzLf9BajCw0cSdyXOe4o/w640-h424/Screenshot+2021-07-27+at+11.13.19+AM.png" width="640" /></a></div><div><br /></div><div><b>Trusted Site Name:</b> GithubAPI</div><div><b>Trusted Site URL:</b> https://api.github.com</div><div><b>Description:</b> GitHub API</div><div><b>Active:</b> true</div><div><b>Context:</b> LEX</div><div><b>Allow site for connect-src:</b> true</div><div><b>Allow site for img-src:</b> true</div><div><br /></div><div>3. Click on <b>Save</b> button.</div><div><br /></div><div>A new record will be created as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzDD5Ul0-JSeOTLZ2RHQid8K693fbjMd1letdqJ057c5DbE6QJ3C_uDrSIzKRWYGuZRcNehtn4taks8K-Dn6lE-ttW0EaVgfhiMe0VEaC1GbIlSNLj9dEBKmiwqFPL-DCXTqqxHEeI1xA/s2400/Screenshot+2021-07-27+at+11.32.43+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="654" data-original-width="2400" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzDD5Ul0-JSeOTLZ2RHQid8K693fbjMd1letdqJ057c5DbE6QJ3C_uDrSIzKRWYGuZRcNehtn4taks8K-Dn6lE-ttW0EaVgfhiMe0VEaC1GbIlSNLj9dEBKmiwqFPL-DCXTqqxHEeI1xA/w640-h174/Screenshot+2021-07-27+at+11.32.43+AM.png" width="640" /></a></div><div><br /></div>Now, refresh the page and try to get the information from GitHub API again by entering a username. This time, you should get a correct response as shown below:</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5NbEHmI23odmVkomBY2YPM2VNomFDSVCmqkBD91J5QeB6FmYHdWO6rPS0Hk01qUNaapT7rT7nwUoRrzw3Q4m7peKwnXvM7oNlub6cjm-t4YIzPt0AWnZNiNojiz3NFvSuKRmfmsrZnt0/s1592/Screenshot+2021-07-27+at+11.30.40+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="418" data-original-width="1592" height="168" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5NbEHmI23odmVkomBY2YPM2VNomFDSVCmqkBD91J5QeB6FmYHdWO6rPS0Hk01qUNaapT7rT7nwUoRrzw3Q4m7peKwnXvM7oNlub6cjm-t4YIzPt0AWnZNiNojiz3NFvSuKRmfmsrZnt0/w640-h168/Screenshot+2021-07-27+at+11.30.40+AM.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">and the information will be displayed in the component as follows:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRjg5t1EYK5vTCiLxmCoUn5zfy8eWZBgvl890IiTZUU32iienexWFkkBmNNI8z2-fvb7G0NcemF4271j6-4jKVotlyZBncuame0pG5UfH8SRwV2KLHlO-7tCztjmaj9qvwLit4Nk01A8g/s1226/Screenshot+2021-07-27+at+12.22.47+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1226" data-original-width="942" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRjg5t1EYK5vTCiLxmCoUn5zfy8eWZBgvl890IiTZUU32iienexWFkkBmNNI8z2-fvb7G0NcemF4271j6-4jKVotlyZBncuame0pG5UfH8SRwV2KLHlO-7tCztjmaj9qvwLit4Nk01A8g/w308-h400/Screenshot+2021-07-27+at+12.22.47+PM.png" width="308" /></a></div><div><div><div><h3 style="text-align: left;">Conclusion</h3><div>You can use the <b>fetch()</b> method to hit any external API from lwc component. We can also add more data in the fetch request, for example, in case of a POST request, you may need to send a request body as well along with some headers. You can also send the request data as an object which can be passed as the 2nd parameter of the fetch() method. The basic syntax for that is shown below:</div><div><div><pre>fetch('<request-url>', {
method: '<method-name>', // * Like: GET, POST
headers: {
<span> </span>'Content-Type': '<content-type-passed-in-body>' // * Like: application/json, application/x-www-form-urlencoded
},
body: JSON.stringify(data) // * Please note that body data type must match "Content-Type" header
});
</pre></div><div>The <b>then()</b> and <b>catch()</b> methods followed by this <b>fetch()</b> method will remain the same.</div><div><br /></div><div>That's all for this tutorial everyone, I hope you understood how you can call an external api from lwc using Fetch API. If you want to learn more about Fetch API in detail you can learn about it <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" target="_blank"><span style="color: #2b00fe;">here</span></a>. Let me know your feedback in the comments down below. You can find the full code for this tutorial in the <b>fetch-api</b> branch of <b>salesforce-lwc-concepts</b> github repository <a href="https://github.com/rahulmalhotra/salesforce-lwc-concepts/tree/fetch-api/force-app/main/default/lwc/githubInfo" target="_blank"><span style="color: #2b00fe;">here</span></a>.</div><div><br /></div><div><b>Happy Trailblazing..!!</b></div></div></div></div></div>Rahul Malhotrahttp://www.blogger.com/profile/12391262239143475497noreply@blogger.com0