SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel

Sunday, 24 October 2021

System.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: []

Hello Trailblazers,


In this post we're going to see the solution of the error "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: []" OR "System.DmlException: Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []"


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.


Inserting Account and Contact record together

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:

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: Reference_ID__c. In case of contact record, I have referred to the parent account record by populating the relationship field "Account" (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. Give it a try!!

Let's do a very small change now:

As you can see above, now we've also specified AccountId in contact record and that too as null which probably should have no impact. Let's execute the code and see what we get:


As you can see we're getting the same error 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: []

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 Parent and a salesforce id, ParentId: []" 

Here our Parent is Account and ParentId is AccountId these can be different depending upon your field name and what objects you're dealing with. This simply means:

"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"

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.

Happy Trailblazing!!

Saturday, 2 October 2021

First error: Exceeded max size limit of 6000000 OR Exceeded max size limit of 12000000 - Solution

Hello Trailblazers, in this post we're going to talk about the resolution of a very common error as given below:

First error: Exceeded max size limit of 12000000 OR First error: Exceeded max size limit of 6000000 

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.

How to resolve this error?

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:

1. Use Limits.getHeapSize() to understand how much heap size (approx) is used in the current transaction upto a particular point.

2. Use Limits.getLimitHeapSize() to get the total amout of heap size available in the current transaction.

3. Use SOQL For Loops, 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).
List<Account> accounts = [SELECT Id FROM Account];
// Stores 50,000 records at max - greater heap size
for(List<Account> accounts : [SELECT Id FROM Account]) {
}
// Stores 200 records at max - lesser heap size

4. Manage your code effectively using helper functions - Avoid class level variables.
void callMe()
{
    List<Account> accounts = [SELECT Id FROM Account];
    System.debug(Limits.getHeapSize()); // Coming as: 321085
}
callme();
System.debug(Limits.getHeapSize()); // Coming as: 1044

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.

5. Clear the lists/map/set/variables which are not in use or make them null



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. 

That's all for this tutorial everyone I hope you liked it, the full code used in this post can be found here. Do let me know your feedback/thoughts in the comments down below.

Sharing a Salesforce Help Article related to this error: https://help.salesforce.com/s/articleView?id=000321537&type=1

Happy Trailblazing!!

Friday, 10 September 2021

List Data Structure in Apex | Apex Data Structure Tutorials by SFDC Stop

Hello Trailblazers,


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.

Tutorial Video

The syntax to create a new list is as follows:

List<DataType> listName = new List<DataType>();


Here, DataType should be replaced by the type of data you would like to store and listName 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:

List<Integer> numbers = new List<Integer>();

The above code snippet will create a list of Integers named as numbers. 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:

1. Creating a list of Integers


Code Snippet:

List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);


Output:

2. Creating a list of Strings


Code Snippet:

List<String> names = new List<String>{'Richard', 'Gilfoyle', 'Dinesh', 'Jared', 'Monica'};
System.debug(names);


Output:


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:

Adding elements to the list

You can use the add() method to add elements to the list. Let's consider the below example:

Code Snippet:
List<Integer> numbers = new List<Integer>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
System.debug(numbers);

Output:


As you can see above, first of all I initialized a list and then added 5 elements to the list one by one using add() method. Here also, our list is created successfully with 5 elements: 1, 2, 3, 4 and 5.

Accessing elements from the list

Each element in a list is associated with an index starting from 0, for example: the list above contains 5 elements which are stored in the memory as follows:
Let's say you want to access element at index 3, you can simply refer to that by: numbers[3] or you can also use the get() method as: numbers.get(3). Considering the above list, the element at index 3 is: 4. Let's try to code this and see the output:

Code Snippet:
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));

Output:

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 square brackets [] as well as by using the get() method.

Adding element at a particular index

If you want to add element at a particular index, you can use the add() method to do so. Just pass the index as the first parameter and the value as the second parameter to the add() method. For example:


Code Snippet:

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);


Output:

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:
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:

Removing element from the list

We can remove an element from the list by using the remove() 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:

Code Snippet:
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);

Output:

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.

Other Commonly used List methods

clear() - Clear all elements in the list

Code Snippet:
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);

Output:
As you can see above, we created a list of numbers, we used clear() method to clear the list. Then we displayed the list got a confirmation that it's empty.

isEmpty() - Returns true if the list is empty, otherwise returns false

Code Snippet:
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());

Output:
As you can see above, initially, the list was empty, so the isEmpty() method is returning true when called on the numbers list. After that, we added "1" to the list and then the isEmpty() method is returning false.

size() - Returns the size of the list i.e. the total number of elements present in the list

Code Snippet:
List<Integer> numbers = new List<Integer>{1,2,3,4,5};
System.debug(numbers);
System.debug('Size of list = ' + numbers.size());

Output:
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.

contains() - Returns true if a particular element is present in the list, otherwise, returns false

Code Snippet:
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));

Output:
As you can see above, we had 5 elements in the list initially: 1, 2, 3, 4, 5. We checked for element 3 using contains() and it returned true 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 contains() and this time, the result comes out to be false.

sort() - Sort the elements in the list in ascending order

Code Snippet:
List<Integer> numbers = new List<Integer>{1,4,5,3,2};
System.debug(numbers);
System.debug('Sorting list...');
numbers.sort();
System.debug(numbers);

Output:
As you can see above, initially, the list had elements: 1, 4, 5, 3, 2. Then we sorted the list using sort() and now, the list consist of elements in sorted order i.e.: 1, 2, 3, 4, 5.

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 official salesforce documentation for more information.

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 here.


Happy Trailblazing..!!

Monday, 30 August 2021

Custom validation in Lightning Web Component | Understanding Regular Expressions (Regex)

Hello Trailblazers,

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:

Tutorial Video

HTML Code

<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>
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 required property while defining lightning-input to make them required. Notice that we don't have to add something like required="true" here because required is a boolean property and we can set it as true by just mentioning it in the tag.

Another thing to notice here is, all the fields that I want to validate are specified with a class named validate 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 lightning-combobox 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 countryOptions which we're going to define in js, in the options property. Finally, the lightning-button is going to call createContact() method in js when it's clicked.

Before moving onto the js, let's add more validation for some of the fields:

Setting up validation for Email field

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.

Building a Regex

Building a simple regex is fairly easy. First of all let's list down the criterias that our email field value should fulfil:

  1. Email can start with characters between A-Z, a-z, 0-9, can have an underscore "_", a hyphen "-", or a dot "." in between. For example: rahul.malhotra, RAHUL.MALHOTRA, rahul123.malhotra, rahul_malhotra, rahul-malhotra etc.

  2. After that it should be followed by an @ symbol and we should allow only these characters post @: a-z, 0-9 and a hyphen "-". For example: rahul.malhotra@example, rahul.malhotra@sfdc12, rahul.malhotra@sfdc-stop etc.

  3. Finally, we should have a dot "." followed by a minimum of 2 characters in the range: a-z. For example: rahul.malhotra@sfdcstop.com, rahul@example.in etc.

So, that's how the full email address should be formed fulfilling these conditions. Let's start creating regex now:

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: "[A-Za-z0-9._-]+". If you notice all the acceptable characters are included in square followed by a + which means that we can have 1 or more occurence of any of these characters. This regex will validate the part of email before @ symbol.

2. Then we should have an @ symbol, let's add that to regex so it becomes: "[A-Za-z0-9._-]+@" 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: "[a-z0-9-]+". 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: "[A-Za-z0-9._-]+@[a-z0-9-]+"

3. Now, as per point 3, we should have a dot "." followed by two or more characters in the range of lowercase alphabets a to z. So, the regex for this will be: "\.[a-z]{2,}$". As you can see first of all we have a dot escaped by "\" character as: "\." . 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 {2,} which means we should have two or more occurences of the characters specified in square brackets. So, our regex is complete and each regex always ends with a $ to specify the end, therefore we have added a $ at the end.

The final regex for our email validation is: [A-Za-z0-9._-]+@[a-z0-9-]+\.[a-z]{2,}$

We can specify a regex by using the pattern property of the lightning-input, so our updated email field will be:
<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>
As you can see above, I have added my regex in the pattern field and have added another property named as message-when-pattern-mismatch with a value: "Please enter a valid email". The message-when-pattern-mismatch property is used to specify an error message when the input entered by user is not matching the pattern specified in the pattern property.

Our email field is fixed now, let's add a validation for our phone field as well:

Setting up validation for Phone field

For phone field, a simple phone number can be in the format: 123-456-7890. That means, we can have 3 numbers followed by a hyphen "-", followed by another 3 numbers and then again a hyphen "-" followed by 4 numbers at the end.

Therefore, the regex will be: [0-9]{3}-[0-9]{3}-[0-9]{4}$

Here, [0-9]{3} represents: we're allowing 3 characters in the range 0-9. Similarly for 4 numbers we have specified [0-9]{4}.

For the error message, we're going to specify Please enter a valid phone number as a value for the message-when-pattern-mismatch property. The updated phone field will be:
<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>
Our phone field is also fixed now, let's add a validation for pin code as well.

Setting up validation for Pin Code field

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 min as 1000 and max as 999999. We also have other properties to setup if the user enters a value outside the range. message-when-range-overflow property is used to specify an error message which will be displayed when a value is greater than the value specified in max. Similarly, message-when-range-underflow property is used to specify an error message which will be displayed when a value is less than the value specified in min.

So, the updated pin code field will be:
<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>

Let's have a look at our updated form as a whole:
<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>

JavaScript Code

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:
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);
        }
    }
}
In the above code snippet, contact is just used to store the data that's entered by user. countryOptions is storing a list of countries with their label and values as these countries are displayed in lightning-combobox. After that we have two methods: isInputValid() and createContact().

isInputValid()

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 isValid which is true by default. Then we're getting all the input fields using this.template.querySelectorAll('.validate'); if you remember, we've given the same class name validate to all the input fields that we want to validate. We're using the same validate class here to get references of all input fields together in order to form inputFields array.

Then, we're iterating this inputFields array using a forEach loop and for each inputField, we're first of all checking the validity using checkValidity() inside an if condition. If the input field is invalid that means, we have a validation error. We're reporting that validity by calling reportValidity() method on inputField as: inputField.reportValidity(); . This method will mainly show the error message on the field. Then, we're setting up isValid to false as the inputField is not valid. Outside the if condition, we're populating our contact object by using inputField.name as the key and inputField.value 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 isValid variable (which is a boolean) from the isInputValid() method.

createContact()

As the sign up button is clicked in html, the createContact() method is called, Inside this method, first of all we're calling the isInputValid() 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 contact object in the js console to verify the values.

Validation Demo


If we have filled all the field values properly, we'll see the contact object in the console as shown below:

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 here.

Happy Trailblazing..!!

Monday, 23 August 2021

Understanding Dynamic Apex and it's Use Cases

Hello Trailblazers,

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.

Tutorial Video


What is Dynamic Apex?

As per the Salesforce Documentation, Dynamic Apex enables developers to create more flexible applications. In other words, we can say that using dynamic apex, you can write Generic Code 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.

Get all object API names with their labels

Sometimes, you need to get a list of all sObjects that are present in your org. Let's have a look at the sObject convertor screenshot to understand the use case:


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:
// * 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));
}
As you can see in the above code snippet, first of all we're initializing a map of string,string named objectAPINameToLabelMap. Then, we're getting all sObjects metadata from salesforce using getGlobalDescribe() method of Schema class. This method will return a map of sObject name as key and an object of SObjectType class as value. The SObjectType class is defined in Schema namespace.

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 currentSObjectType variable. From the currentSObjectType, we're using the getDescribe() method to get it's metadata which is an object of DescribeSObjectResult class defined in Schema 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 getName() and getLabel() methods and store them in objectAPINameToLabelMap.

Important Note: The Schema in Schema.SObjectType and Schema.DescribeSObjectResult is a namespace whereas, the Schema in Schema.getGlobalDescribe() is a class. Please don't get confused between the two.

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:


As you can see in the above output, some of the internal objects may not have a label as well. For example, AppointmentTopicTimeSlotFeed 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.

Checking object permissions for the logged in user

We can also get the sObjectType by using the getSObjectType() method directly on the object api name. Let's consider the below code:
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());
As you can see above, first of all we're getting the SObjectType by using the getSObjectType() method on AppointmentTopicTimeSlotFeed object. Then, we're getting it's DescribeSObjectResult by using the getDescribe() method as we have done before. Then, we're displaying some properties of the object like:
  • Object API Name
  • Is the object a custom object?
  • Is the object accessible?
  • Is the object creatable?
  • Is the object updateable?
  • Is the object deletable?
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. Let's have a look at the output below:


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. Depending upon these properties, we decide whether to consider this object in our generic code for business logic or not.

Get all the fields for an object

Sometimes it's required to get a list of fields for a standard/custom sobject. We used the same in sObject Convertor as well because after selecting the objects, we need to define field mapping.
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:
// * 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);
}
As you can see above, first of all we've initialized a set of string accountFieldNames. Then we're getting sobject type of account and storing it in accountObjectType variable. We're also storing describe result of account in accountSObjectResult. Once, we have the account metadata, it's time to get the field metadata now. For that, we've used accountSObjectResult.fields.getMap() to get the fields map for account. (fields 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 accountField variable which is of type Schema.SObjectField. This is similar as we used SObjectType in case of object.

Now, we have the field reference, we'll extract the metadata from field by using getDescribe() method on the accountField variable. It will return an instance of Schema.DescribeFieldResult which we're storing in accountFieldResult variable. Now, we can use different methods of DescribeFieldResult class to get different metadata about the field. Finally, we're retrieving the API name of each field using the getName() method and storing it in accountFieldNames set.

At last, we're iterating the set and displaying the field names. The output when this code is executed is given below:
If you notice, the above code is specific to account, we can also make it a little more generic by using getGlobalDescribe() method that we learned in the previous section. Have a look at the code below:
/*
*   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);
}
In the above code, we've defined a method named getSObjectFields() 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 getGlobalDescribe() to get the sObjectMap and then used the sObjectName variable to get the sObjectType. We've also reduced the code by iterating over fieldMap.values() to get a reference to SObjectField directly instead of getting it from map. We're adding the field name to the fieldNames set only if the current field is accessible so that they can be queried later. The result for account fields will be the same as shown below:
We modified the code a little to call this method for Employee__c which is a custom object defined in salesforce org.
Set<String> fieldNames = getSObjectFields('Employee__c');
for(String fieldName : fieldNames) {
    System.debug(fieldName);
}
The result of the modified code is given below:
As you can see, we're getting the fields for Employee__c object now. This is the same employee object that we used in Find the Lead Manager Tutorial. 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!

Dynamic SOQL

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:
/*
*   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);
In the above code we have defined a method named getSOQL 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 getSObjectFields() 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. 

We tried the same method to form a query for Contact object and the result when this code is executed is given below:

In case, we want to query Account fields instead of Contact, we just need to pass Account in the getSOQL() method as: getSOQL('Account') and we'll get the resultant SOQL. The output for the same is given below:

See, How easy it is now to form a query for any sObject with all fields by using Dynamic Apex. 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:
This is an implementation of Dynamic SOQL. As we're creating the SOQL query dynamically by adding all the fields and using that. 

Note: 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.

Get picklist field values

By now, you know how do we get a describe field result using dynamic apex right? The DescribeFieldResult class is having a method named as: getPicklistValues() with a return type as List<Schema.PicklistEntry> 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 - How can we identify if the field is picklist or not?

We have a method named: getType() in the DescribeFieldResult class which will return an enum 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:
/*
*   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);
}
We have defined a method named: getPicklistValuesMap() above which is going to accept two parameters: objectAPIName and fieldAPIName. This method will return the picklistFieldMap 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 fieldResult 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 getType() method which will return a DisplayType enum value.

If the current field is a picklist, we're getting the values of it using getPicklistValues() which will return a List<Schema.PicklistEntry>. Each PicklistEntry 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.

In order to test our method, we've tried to get the Type picklist values from the Account object and displayed those by iterating over the map using a loop. The output for the same is given below:
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.

Getting the Record Type Id by using Record Type Name

We created 3 account record types in salesforce org as shown below:
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:
/*
*   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'));
As you can see above, we have defined a method named getRecordTypeIdsByName which will accept objectAPIName in the parameter and will return recordTypesMap 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 List<Schema.RecordTypeInfo>. In this list, each entry will be an instance of RecordTypeInfo 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 Active and Available 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.

We're testing this method by getting all record types for account object. Let's see the output below:
We're getting the correct output i.e. the label of the record type with it's id.
Note: If you want to use the developer name of the record type, you can use getDeveloperName() method instead of getName().

Dynamic SOSL

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 sObject Convertor 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.

Wrapping it Up

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 here 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.

Happy Trailblazing..!!

Monday, 16 August 2021

Call External API from Lightning Web Component | Fetch API in JavaScript

Hello Trailblazers,


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 fetch() method which can be used in JavaScript to perform any kind of callout to an external system across the network and get the data.


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 true if the callout is successful and it's set to false 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.

Tutorial Video



To learn about fetch(), we're going to create a lwc component to get the details of a user from GitHub as shown below:



Let's have a look at the below code snippets of this component along with the explanation:

githubInfo.html

<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>
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 username variable which we're going to define in js and it's going to call updateUsername() method whenever we're typing something in this input field so that we can update the username accordingly.

We also have a button here which is going to call getGithubStats() method from js whenever this button is clicked. The getGithubStats() will be used to fetch data from github using the Fetch API and the details will be displayed in the user details section.

To display the details of user, we've created a user object in js and we're checking if the userPopulated 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.

Now, let's have a look at the js code quickly:

githubInfo.js

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');
        }
    }

}
The above code consist of a GITHUB_URL constant which is basically storing our base URL for GitHub API. Inside the class, we have two data members: username and user as discussed before. We also have a userPopulated() 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.

After that, we also have a githubURL() getter which is going to form the profile URL of user, based on the username entered. Then we have an updateUsername() method, which is called automatically while updating text in the username input field, it's updating the username data member with the latest value. Finally, we have a getGithubStats() method which is performing the callout in order to get the user details from github on click of a button.


getGithubStats() - 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: Please specify a username in the alert. You can also use a toast here, I have just added an alert to keep it simple.

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 fetch() 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: GITHUB_URL + this.username. After that, we've two then() followed by a catch(). The first then() is going to receive a Response object from the Fetch API. We are using it's ok 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 response.json() which will be received by subsequent then(). If we receive an error, we're throwing an instance of Error by passing the response in the constructor. The subsequent then() will store the JSON result in githubUser object which is used to populate the user data member of the class in order to display the user's data.

In case of an error, we're simply displaying it using console.log().

To give you a reference of the Github API response, I am displaying it below:
{
  "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"
}
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:
.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
    };
})

It's time to look at the meta file now:

githubInfo.js-meta.xml

<?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>
We've exposed this component by setting up isExposed as true and added a single target named as: lightning__HomePage as we want to embed this component in the home page.

Setting up CSP Trusted Sites

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 Search button.


However, we received the below error in console:


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:

1. Go to setup and search for CSP Trusted Sites


2. Click on New Trusted Site button and fill up the information as shown below:


Trusted Site Name: GithubAPI
Trusted Site URL: https://api.github.com
Description: GitHub API
Active: true
Context: LEX
Allow site for connect-src: true
Allow site for img-src: true

3. Click on Save button.

A new record will be created as shown below:


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:


and the information will be displayed in the component as follows:

Conclusion

You can use the fetch() 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:
fetch('<request-url>', {
    method: '<method-name>', // * Like: GET, POST
    headers: {
        '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
});
The then() and catch() methods followed by this fetch() method will remain the same.

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 here. Let me know your feedback in the comments down below. You can find the full code for this tutorial in the fetch-api branch of salesforce-lwc-concepts github repository here.

Happy Trailblazing..!!