Friday, June 28, 2019

How to Delete multiple selected records in Lightning Datatable in lightning web components(lwc)

This post explains how to delete multiple selected records in lightning datatable in lightning web components(lwc)



Example
deleteRowsInDatatableLWC.html
<template>
     <lightning-card title="Delete Selected Rows in Datatable" icon-name="standard:contact" > <br/>
        <div style="width: auto;">
            <template if:true={data}>
                    <div class="slds-grid slds-gutters">
                            <div class="slds-col">
                              <span><p style="margin-left: 5%">Selected Records: <b style="color:red;">{recordsCount}</b></p></span>
                            </div>
                            <div class="slds-col">
                                <span>                    
                                    <lightning-button label={buttonLabel}
                                                      icon-name="utility:delete"
                                                      disabled={isTrue}
                                                      variant="destructive" 
                                                      onclick={deleteAccounts}
                                                      style="margin-left: 40%"></lightning-button>
                                </span>
                            </div>
                        </div><br/>

                    <lightning-datatable data={data}
                                     columns={columns}
                                     key-field="id"
                                     onrowselection={getSelectedRecords}></lightning-datatable>
            </template>

        </div>
    </lightning-card>   
</template>
deleteRowsInDatatableLWC.js
import {LightningElement, track, wire} from 'lwc';

// importing apex class methods
import getContacts from '@salesforce/apex/LWCExampleController.getContacts';
import delSelectedCons from '@salesforce/apex/LWCExampleController.deleteContacts';

// importing to show toast notifictions
import {ShowToastEvent} from 'lightning/platformShowToastEvent';

// importing to refresh the apex after delete the records.
import {refreshApex} from '@salesforce/apex';

// datatable columns
const columns = [
    {
        label: 'FirstName',
        fieldName: 'FirstName'
    }, {
        label: 'LastName',
        fieldName: 'LastName'
    }, {
        label: 'Phone',
        fieldName: 'Phone',
        type: 'phone'
    }, {
        label: 'Email',
        fieldName: 'Email',
        type: 'email'
    }
];

export default class DeleteRowsInDatatableLWC extends LightningElement {
    // reactive variable
    @track data;
    @track columns = columns;
    @track buttonLabel = 'Delete Selected Contacts';
    @track isTrue = false;
    @track recordsCount = 0;

    // non-reactive variables
    selectedRecords = [];
    refreshTable;
    error;

    // retrieving the data using wire service
    @wire(getContacts)
    contacts(result) {
        this.refreshTable = result;
        if (result.data) {
            this.data = result.data;
            this.error = undefined;

        } else if (result.error) {
            this.error = result.error;
            this.data = undefined;
        }
    }


    // Getting selected rows 
    getSelectedRecords(event) {
        // getting selected rows
        const selectedRows = event.detail.selectedRows;
        
        this.recordsCount = event.detail.selectedRows.length;

        // this set elements the duplicates if any
        let conIds = new Set();

        // getting selected record id
        for (let i = 0; i < selectedRows.length; i++) {
            conIds.add(selectedRows[i].Id);
        }

        // coverting to array
        this.selectedRecords = Array.from(conIds);

        window.console.log('selectedRecords ====> ' +this.selectedRecords);
    }


    // delete records process function
    deleteAccounts() {
        if (this.selectedRecords) {
            // setting values to reactive variables
            this.buttonLabel = 'Processing....';
            this.isTrue = true;

            // calling apex class to delete selected records.
            this.deleteCons();
        }
    }


    deleteCons() {
        delSelectedCons({lstConIds: this.selectedRecords})
        .then(result => {
            window.console.log('result ====> ' + result);

            this.buttonLabel = 'Delete Selected Contacts';
            this.isTrue = false;

            // showing success message
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Success!!', 
                    message: this.recordsCount + ' Contacts are deleted.', 
                    variant: 'success'
                }),
            );
            
            // Clearing selected row indexs 
            this.template.querySelector('lightning-datatable').selectedRows = [];

            this.recordsCount = 0;

            // refreshing table data using refresh apex
            return refreshApex(this.refreshTable);

        })
        .catch(error => {
            window.console.log(error);
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error while getting Contacts', 
                    message: error.message, 
                    variant: 'error'
                }),
            );
        });
    }  

}
deleteRowsInDatatableLWC.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="DeleteRowsInDatatableLWC">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>
Apex Class
public inherited sharing class LWCExampleController {

    @AuraEnabled(Cacheable = true)
    public static List<Contact> getContacts(){
        return [SELECT Id, FirstName,LastName, Phone, Email FROM Contact ORDER BY Name limit 10];
    }
    
    @AuraEnabled
    public static void deleteContacts(list<Id> lstConIds){
        try {
            list<Contact> lstConsToDelete = new list<Contact>();
            System.debug('lstConIds ====> '+lstConIds);
            for(Id idCon : lstConIds) {
                lstConsToDelete.add(new Contact(Id = idCon));
            }
            if(!lstConsToDelete.isEmpty()) {
                delete lstConsToDelete;
            }
        }
        catch(Exception ex) {
            throw new AuraHandledException(ex.getMessage());
        }
    }
}
Output


Thursday, June 27, 2019

Send data from Lightning Component to Visualforce page Using Custom Event

This post explains how to send the data from the Lightning component to the visualforce page by using Custom Event.

In this demo, I am displaying the Account data in both Lighting component and visualforce page.

Example

Create a Custom Event this event used to send the data to visualforce page

SendDataToVFPage.evt
<!-- this event used in SendDataToVFPageComponent -->
<aura:event type="APPLICATION">
    <aura:attribute name="currentRecId" type="String"/>
    <aura:attribute name="CurrentRecDetails" type="Object" />
</aura:event>
Create Lightning Component and register the custom event in Lighting Component

SendDataToVFPage.cmp
<aura:component>

    <aura:attribute name="strParentId" type="String"/>
    <aura:attribute name="recordFields" type="Object"/>

    <!--This event used to send the data from Lightning component to vf page-->
    <aura:registerEvent name="vfEvent" type="c:SendDataToVFPage"/>

    <aura:if isTrue="{!not(empty(v.strParentId))}">
        <force:recordData aura:id="currentRecord" layoutType="FULL" recordId="{!v.strParentId}" targetFields="{!v.recordFields}"/>
        <h3><b>Showing data from Lightning Component</b></h3><br/>
        <div>
            <lightning:card iconName="standard:account" title="{!v.recordFields.Name}">
                <div class="slds-p-horizontal--small">
                    <p class="slds-text-heading--medium"><lightning:formattedText title="Type" value="{!v.recordFields.Type}"/></p>
                    <p class="slds-truncate">
                        <lightning:formattedText title="Industry" value="{!v.recordFileds.Industry}"/></p>
                    <p class="slds-truncate"><lightning:formattedPhone title="Phone" value="{!v.recordFields.Phone}"/></p>
                </div>
            </lightning:card>
        </div><br/>
        <div style="text-align: center">
            <lightning:button variant="brand" label="Send Data VF Page" onclick="{!c.sendDataVFPage}" />
        </div>
    </aura:if>

</aura:component>
when you click the 'sendDataVFPage' button in the Component custom event will fire and it sets the data to the event parameters.
SendDataToVFPageController.js
({
    sendDataVFPage : function(component, event, helper) {
        console.log('current rec details ===> '+JSON.stringify(component.get('v.recordFields')));
        console.log('Parent Id  ===> '+component.get('v.strParentId'));
        if(component.get('v.strParentId')) {
            var myEvent = $A.get("e.c:SendDataToVFPage");
            myEvent.setParams({
                currentRecId: component.get('v.strParentId'),
                CurrentRecDetails: component.get('v.recordFields')
            });
            myEvent.fire();
        }
    }
})

Create Lightning out App to call the lighting component from the visualforce page.
add your component in lightning out App.

DemoLightingOutApp.app
<aura:application extends="ltng:outApp" access="GLOBAL">
    <aura:dependency resource="c:SendDataToVFPage"/>
</aura:application>
Create Visualforce page and include  <apex:includeLightning /> in page.

LightingOutPageDemo.page
<apex:page standardController="Account">
    <apex:pageMessages />
    <apex:includeLightning />
    <div id="vfDemo" />

    <apex:pageBlock title="Showing data from visualforce page">
        <apex:pageBlockSection>
            <apex:pageBlockSectionItem>
                <apex:outputLabel value="Account Name" />
                <apex:outputText styleClass="accName" />
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem>
                <apex:outputLabel value="Account Industry" />
                <apex:outputText styleClass="accIndustry" />
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem>
                <apex:outputLabel value="Account Phone" />
                <apex:outputText styleClass="accPhone" />
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
    </apex:pageBlock> <br/><br/>

    <script>
        // Calling Lightning component from vf page
        $Lightning.use("c:DemoLightingOutApp", function () {
            $Lightning.createComponent("c:SendDataToVFPage", {
                strParentId: '{!Account.Id}', // passing parameters to Lightning Component
            },
                "vfDemo",
                function (component) {
                    console.log("Lightning component rendered successfully!!");
                    // Event Service hander to handele the lightning component cusom event
                    $A.eventService.addHandler({ "event": "c:SendDataToVFPage", "handler": retriveEventData });
                });
        });


        function retriveEventData(event) {
            var recordTypeId = event.getParam("currentRecId");
            var eventRecordData = event.getParam("CurrentRecDetails");

            // passing data to outputtext lables
            document.getElementsByClassName("accName")[0].innerHTML = eventRecordData.Name;
            document.getElementsByClassName("accIndustry")[0].innerHTML = eventRecordData.Industry;
            document.getElementsByClassName("accPhone")[0].innerHTML = eventRecordData.Phone;
        }

    </script>
</apex:page>
Create Detail page button on the account select button type as visualforce and add the button to the Page Layout.
That's it.
Clcik the button from account record the component will render from vf page.

Result

Wednesday, June 26, 2019

Custom File Upload in Lightning Web Components(lwc)

This post explains how to use custom file upload in Lightning web components(lwc).

Note
This component only works for small size files, it will not work for the big size files.

Example

customFileUploader.html
<template>
    <lightning-card title="Custom File Uploader">
            <div style="margin-left:4%">
                <div>
                    <lightning-input label="" name="file uploader" onchange={handleFilesChange} type="file" multiple></lightning-input>
                </div><br/>
                <div class="slds-text-body_small slds-text-color_error">{fileName}
                <template if:true={showLoadingSpinner}>
                        <lightning-spinner alternative-text="Uploading......" size="medium"></lightning-spinner>
                    </template>
                </div><br/>
                <div>
                    <lightning-button class="slds-m-top--medium" label={UploadFile} onclick={handleSave} variant="brand" disabled={isTrue}></lightning-button>
                </div>
            </div><br/><br/>
            <!-- Datatable to show the related files of the record -->
                <lightning-card title="Related Files of the record" icon-name="standard:account">
                    <div style="width: auto;">
                        <template if:true={data}>
                                <lightning-datatable
                                        data={data}
                                        columns={columns}
                                        key-field="id"
                                        onrowselection={getSelectedRecords}>
                                </lightning-datatable>
                        </template>
                    </div>
                </lightning-card>
    
        </lightning-card>
</template>

customFileUploader.js
import { LightningElement, track, api } from 'lwc';
import saveFile from '@salesforce/apex/LWCExampleController.saveFile';
import releatedFiles from '@salesforce/apex/LWCExampleController.releatedFiles';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';

const columns = [
    {label: 'Title', fieldName: 'Title'}
];

export default class CustomFileUploader extends LightningElement {
    @api recordId;
    @track columns = columns;
    @track data;
    @track fileName = '';
    @track UploadFile = 'Upload File';
    @track showLoadingSpinner = false;
    @track isTrue = false;
    selectedRecords;
    filesUploaded = [];
    file;
    fileContents;
    fileReader;
    content;
    MAX_FILE_SIZE = 1500000;


    connectedCallback() {
        this.getRelatedFiles();
    }

    // getting file 
    handleFilesChange(event) {
        if(event.target.files.length > 0) {
            this.filesUploaded = event.target.files;
            this.fileName = event.target.files[0].name;
        }
    }

    handleSave() {
        if(this.filesUploaded.length > 0) {
            this.uploadHelper();
        }
        else {
            this.fileName = 'Please select file to upload!!';
        }
    }

    uploadHelper() {
        this.file = this.filesUploaded[0];
       if (this.file.size > this.MAX_FILE_SIZE) {
            window.console.log('File Size is to long');
            return ;
        }
        this.showLoadingSpinner = true;
        // create a FileReader object 
        this.fileReader= new FileReader();
        // set onload function of FileReader object  
        this.fileReader.onloadend = (() => {
            this.fileContents = this.fileReader.result;
            let base64 = 'base64,';
            this.content = this.fileContents.indexOf(base64) + base64.length;
            this.fileContents = this.fileContents.substring(this.content);
            
            // call the uploadProcess method 
            this.saveToFile();
        });
    
        this.fileReader.readAsDataURL(this.file);
    }

    // Calling apex class to insert the file
    saveToFile() {
        saveFile({ idParent: this.recordId, strFileName: this.file.name, base64Data: encodeURIComponent(this.fileContents)})
        .then(result => {
            window.console.log('result ====> ' +result);
            // refreshing the datatable
            this.getRelatedFiles();

            this.fileName = this.fileName + ' - Uploaded Successfully';
            this.UploadFile = 'File Uploaded Successfully';
            this.isTrue = true;
            this.showLoadingSpinner = false;

            // Showing Success message after file insert
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Success!!',
                    message: this.file.name + ' - Uploaded Successfully!!!',
                    variant: 'success',
                }),
            );

        })
        .catch(error => {
            // Showing errors if any while inserting the files
            window.console.log(error);
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error while uploading File',
                    message: error.message,
                    variant: 'error',
                }),
            );
        });
    }
    
    // Getting releated files of the current record
    getRelatedFiles() {
        releatedFiles({idParent: this.recordId})
        .then(data => {
            this.data = data;
        })
        .catch(error => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error!!',
                    message: error.message,
                    variant: 'error',
                }),
            );
        });
    }

    // Getting selected rows to perform any action
    getSelectedRecords(event) {
        let conDocIds;
        const selectedRows = event.detail.selectedRows;
        conDocIds = new Set();
        // Display that fieldName of the selected rows
        for (let i = 0; i < selectedRows.length; i++){
            conDocIds.add(selectedRows[i].ContentDocumentId);
        }

        this.selectedRecords = Array.from(conDocIds).join(',');

        window.console.log('selectedRecords =====> '+this.selectedRecords);
    }

}

customFileUploader.js.meta-xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="CustomFileUploader">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

LWCExampleController.cls
public inherited sharing class LWCExampleController {
    
    @AuraEnabled
    public static ContentVersion saveFile(Id idParent, String strFileName, String base64Data) {
        // Decoding base64Data
        base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
        
        // inserting file
        ContentVersion cv = new ContentVersion();
        cv.Title = strFileName;
        cv.PathOnClient = '/' + strFileName;
        cv.FirstPublishLocationId = idParent;
        cv.VersionData = EncodingUtil.base64Decode(base64Data);
        cv.IsMajorVersion = true;
        Insert cv;
        return cv;
    }

    @AuraEnabled
    public static list<contentversion> releatedFiles(Id idParent){
        list<id> lstConDocs = new list<id>();
        for(ContentDocumentLink cntLink : [Select Id, ContentDocumentId From ContentDocumentLink Where LinkedEntityId =:idParent]) {
            lstConDocs.add(cntLink.ContentDocumentId);
        }
        if(!lstConDocs.isEmpty()) {
            return [SELECT Id, Title, ContentDocumentId FROM ContentVersion WHERE ContentDocumentId IN :lstConDocs];
        }
        else {
            return null;
        }
     
    }
    
}

Result

Tuesday, June 25, 2019

How to query Lightning Components Using SOQL


SOQL Query
SELECT Id, DeveloperName FROM AuraDefinitionBundle WHERE DeveloperName = 'CreateNewCaseComponent'

Result

Saturday, June 15, 2019

Send Custom Notifications Using REST API

From Summer 19 you can send custom notifications from process builder or REST API.

Check below post send custom notification using process builder
https://www.salesforcecodecrack.com/2019/06/send-custom-notifications.html

What is the Custom Notifications?
  • Custom notifications allow you to send important information to your users via push to mobile or desktop alerts. 
  • you can fully customize the notifications to inform users of record information, changes, and updates. 
  • Custom notifications will bring relevant information to your users at the right time.
This post explains how to send custom notifications from REST API

Note:
Before writing the REST API class you need to define the custom notification type in notification types.
Path:
From Setup, enter Notification builder in the Quick Find box, and then Select Notification types

Then Create a new custom notification type and you can define where we need to show notification Desktop or Mobile


Once you created the Notification type, now ready to create REST API Class.

Example:

Read comments to understand the class and check sample responses added within the class to understand.
I created the classes in a generic way you can use with any object,  just you need to pass the required data to the class
Required data to send Custom Notification
  • Custom Notification type Id
  • Recipients(for who need to send notifications User, Owner, Group and Queue Ids)
  • Notification title
  • Notification body
  • Target Record Id
Sample JSON Body
Custom Notification body look like this
{
"inputs" :
        [
         {
          "customNotifTypeId" : "0MLB0000000PAwjOAG",
          "recipientIds" : ["005B0000005lewTIAQ"],
          "title" : "opportunity Closed!",
          "body" : "Your Opportunity has been Closed.",
          "targetId" : "006B00000058gHEIAY"
         }
        ]
  }

CustomNotificationRESTAPI.apxc
/* 
Purpose: This class sends the custom notifications to the recipients(User, Owner, Queue, Group).
Author: Shaik Nagajani
Date:15/June/2019
*/

public inherited sharing class CustomNotificationRESTAPI {
 
    public static void sendCustomNotification(String strNotifiTypeDevName, list<sObject> lstObjects, list<Id> lstRecipients) {
        String strResponse = null;
        if(String.isNotBlank(strNotifiTypeDevName)) {
            // Tooling API query
            String strResourceQuery = 'tooling/query/?q=select+id,customNotifTypeName+from+CustomNotificationType+where+DeveloperName=\''+ strNotifiTypeDevName +'\'';
            
            // OrgREST Custom Class returns the response as string
            strResponse = OrgREST.retriveResult(strResourceQuery, 'GET', null);
        }
        
       /* 
          Sample Response
          ----------------
          {
              done=true, 
              entityTypeName=CustomNotificationType, 
              queryLocator=null, 
              records=({
                CustomNotifTypeName=Opportunity Closed, 
                Id=0MLB0000000PAwjOAG, 
                attributes={
                  type=CustomNotificationType, 
                  url=/services/data/v46.0/tooling/sobjects/CustomNotificationType/0MLB0000000PAwjOAG}
                
              }),
              size=1, 
              totalSize=1
        }
      */
        
        map<String, Object> objData;
        
        if(String.isNotBlank(strResponse)) {
            // Deserializeing the response body
            map<String, Object> mapJsonData = (map<String, Object>)JSON.deserializeUntyped(strResponse);
            
            // Getting records
            list<Object> lstRecordsData = (list<Object>)mapJsonData.get('records');
            
            if(!lstRecordsData.isEmpty()) {
                // Converting each object to readable map to get the data
                objData = (map<String, Object>)lstRecordsData[0];      
            } 
            System.debug('Id ====> '+(String)objData.get('Id'));
        }
        
        /* 
            Sample JSON Body
            ---------------
            { "inputs" :
                 [
                   {
                     "customNotifTypeId" : "0MLB0000000PAwjOAG",
                     "recipientIds" : ["005B0000005lewTIAQ"],
                     "title" : "opportunity Closed!",
                     "body" : "Your High Priority Opportunity has been Closed.",
                     "targetId" : "006B00000058gHEIAY"
                   }
                 ]
             }
        */
        
        
        /* 
         * Generating JSON body.
         * Below code work for bulk record process.
         * Recipients are User, Owner, Group or Queue Ids
        */

        if(!lstObjects.isEmpty() && !lstRecipients.isEmpty()) {
            JSONGenerator jsGen = JSON.createGenerator(true);
            jsGen.writeStartObject();
            jsGen.writeFieldName('inputs');
            jsGen.writeStartArray();
            
            for(sObject obj : lstObjects) {
                jsGen.writeStartObject();
                // Custom Notification type id
                jsGen.writeStringField('customNotifTypeId', (String)objData.get('Id'));
                jsGen.writeFieldName('recipientIds');
                jsGen.writeStartArray();
                
                // adding mulitiple recipients if any 
                for(Id idRecipient : lstRecipients) {
                    jsGen.writeId(idRecipient);
                }
                
                jsGen.writeEndArray();
                // Notification titile
                jsGen.writeStringField('title', (String)obj.get('Name') + 'is Closed');
                // Notification body
                jsGen.writeStringField('body', (String)obj.get('Name') + 'is Closed \n Please connect with customer.');
                jsGen.writeStringField('targetId', (String)obj.get('Id'));
                jsGen.writeEndObject();
            }
    
            jsGen.writeEndArray();
            jsGen.writeEndObject();
            
            // Converting JSON as String
            String strJson = jsGen.getAsString();
            System.debug('strJson ====> '+strJson);
            
            // POST URL to send custom notification
            String strPOSTURL = 'actions/standard/customNotificationAction';
            
            // Sending post request to send custom notification
            String strPostResponse = OrgREST.retriveResult(strPOSTURL, 'POST', strJson);
        }
    }
}

OrgREST.apxc
/*
purpose: This Class gives response based on resource name you entered.
Author: Shaik Nagajani
Date: 15/June/2019
*/

public inherited sharing class OrgREST {
    
    public static String retriveResult(String strResourceName, String strMethod, String strBody) {
        String response;
        String strEndPonitURL = URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v46.0/' + strResourceName;
        if(String.isNotBlank(strResourceName)) {
            HttpRequest httpRequest = new HttpRequest();  
            httpRequest.setMethod(strMethod);   
            httpRequest.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());        
            httpRequest.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID()); 
            
            if(String.isNotBlank(strBody)) {
                httpRequest.setBody(strBody);
                httpRequest.setHeader('Content-Type', 'application/json');
             httpRequest.setHeader('Accept', 'application/json');
            }
            
            httpRequest.setEndpoint(strEndPonitURL); 
            
            try {  
                Http http = new Http();   
                HttpResponse httpResponse = http.send(httpRequest);  
                if (httpResponse.getStatusCode() == 200 ) {  
                    response = httpResponse.getBody();  
                } 
                else {  
                    throw new CalloutException(httpResponse.getBody());  
                }   
            } 
            catch(Exception ex) {  
                throw ex;  
            }  
        } 
        return response;
    }
}

Now everything is ready to test the functionality, to test the functionality run below script from an anonymous window.
list<Opportunity> listOpps = [SELECT Id, Name FROM Opportunity WHERE StageName = 'Closed Won' limit 5];
CustomNotificationRESTAPI.sendCustomNotification('Opportunity_Closed', listOpps, new list<Id>{UserInfo.getUserId(), (Id)'005B0000005lewTIAQ'});

Result


Resources:
HttpRequest Class
HttpResponse Class
CustomNotificationType Tooling API
Custom Notification Actions

Friday, June 14, 2019

How to send custom notifications using process builder

In Summer 19 Salesforce provides the option to send custom notification from process builder.

  • Custom notifications allow you to send important information to your users via push to mobile or desktop alerts. 
  • you can fully customize the notifications to inform users of record information, changes, and updates. 
  • Custom notifications will bring relevant information to your users at the right time.

Note:
Before Customizing the Custom Notification you need to define custom notification type in notification types.
Path:
From Setup, enter Notification builder in the Quick Find box, and then Select Notification types


Then Create a new custom notification type and you can define where we need to show notification Desktop or Mobile


Once you created the Notification type, now ready to create process.

Example: 

In this example, I created the process builder and defined an object as Opportunity, when the record is created or edited it will fire.

I just checked the criteria as whenever the opportunity stage is 'Closed Won'


Select the action type is 'Send Custom Notification'
Select the notification type you created in notification types Select the Notification Recipient such as User, Owner, Group, and Queue(who wants receive the notifications)
Define the notification title and notification body.

Successfully completed the Process creation now it is ready for testing.
To test the process just close any opportunity with stage name is 'Closed Won'.
Result:

Limitations

  • You can create up to 500 custom notification types.
  • Each notification can have up to 10,000 users as recipients. However, you can add an action to the same process within Process Builder or to the same flow in Flow Builder to have more recipients.
  • Your org saves your most recent 1 million custom notifications for view in notification trays. Your org can save up to 1.2 million custom notifications, but it trims the amount to the most recent 1 million notifications when you reach the 1.2 million limit.
  • An org can execute up to 10,000 notification actions per hour. When you exceed this limit, no more notifications are sent in that hour, and all unsent notifications are lost. Notification actions resume in the next hour
  • For example, your notification action processes are triggered 10,250 times between 4:00 and 5:00. Salesforce executes the first 10,000 of those actions. The remaining 250 notifications aren’t sent and are lost. Salesforce begins executing notification actions again at 5:01.
  • Custom notification title and body fields support plain text only.


Resources
https://releasenotes.docs.salesforce.com/en-us/summer19/release-notes/rn_forcecom_flow_notificationbuilder.htm 

Thursday, June 13, 2019

How to retrieve list views of any object using SOQL

Example

SOQL Query
Select DeveloperName, sobjectType From ListView where SobjectType = 'Account'
Result:

How to check object keyprefix using SOQL

Example:

SOQL Query
SELECT DeveloperName, KeyPrefix FROM EntityDefinition WHERE IsCustomSetting = false and QualifiedApiName = 'ContentDocument'
Result

Navigation Service in Lightning Web Components(lwc)


This post explains how to use navigation service in Lightning web components(lwc)

To navigate to many different page types, like records, list views, and objects in lightning web components we need to use lightning/navigation
Note:
  • The lightning/navigation service is supported only in Lightning Experience, Lightning communities, and the Salesforce app. It isn’t supported in other containers, such as Lightning Components for Visualforce, or Lightning Out.
the navigation service uses a PageReference. PageReference is a JavaScript object that describes the page type, its attributes, and the state of the page.

Page type(String) and attributes(Object) are required parameters, state(Object) is optional parameter.

PageReference object must be defined with a specific type. The type generates a unique URL format and defines attributes that apply to all pages of that type.
Currently, Salesforce Supports below PageReference types
  • Lightning Component
  • Knowledge Article
  • Named Page
  • Navigation Item Page
  • Object Page
  • Record Page
  • Record Relationship Page
  • Web Page

Using Navigation Service in LWC

1.  In the component’s JavaScript class, import the lightning/navigation module.
import { NavigationMixin } from 'lightning/navigation';
2.  Apply the NavigationMixin function to your component’s base class.
export default class MyCustomElement extends NavigationMixin(LightningElement) {}
3. Create a plain JavaScript PageReference object that defines the page
4. To dispatch the navigation request, call the navigation service’s [NavigationMixin.Navigate](pageReference, [replace])

Note:
If you are navigating to Lightning Component from LWC, you need to implement the lightning:isUrlAddressable interface in your Lightning Component.

NavigationDemoComponent.cmp
<aura:component implements="lightning:isUrlAddressable" access="global" >
 <lightning:card title="This Component Called From LWC Navigation Service">
     <lightning:badge label="Welcome to Salesforce Code Crack"/>
    </lightning:card>
</aura:component>


Example:

navigationServiceLWCDemo.html
<template>
    <lightning-card title="Navigation Service in Lightning Web Components" icon-name="custom:custom14">
        <br/>
        <lightning-card title="Navigate to Record Pages">
            <lightning-button-group>
                <lightning-button label="New Record Page" onclick={navigateToNewRecordPage}></lightning-button>
                <lightning-button label="Edit Record Page" onclick={navigateToEditRecordPage}></lightning-button>
                <lightning-button label="View Record Page" onclick={navigateToViewRecordPage}></lightning-button>
            </lightning-button-group>
        </lightning-card><br/>

        <lightning-card title="Navigate to List Views">
                <lightning-button-group>
                    <lightning-button label="Account Recent View" onclick={navigateAccRecentView} ></lightning-button>
                    <lightning-button label="Related List View" onclick={navigateRelatedListView}></lightning-button>
                </lightning-button-group>
        </lightning-card><br/>

        <lightning-card title="Navigate to Object Page">
                <lightning-button-group>
                    <lightning-button label="Account Page" onclick={navigateAccObject}></lightning-button>
                    <lightning-button label="Contact" onclick={navigateConObject}></lightning-button>
                </lightning-button-group>
        </lightning-card><br/>

        <lightning-card title="Navigate to Lightning Component">
            <lightning-button label="Lightning Component" onclick={navigateToComponent}></lightning-button>
        </lightning-card><br/>

        <lightning-card title="Navigate to Web Pages">
                <lightning-button label="Web Page" onclick={navigateToWebPage}></lightning-button>
        </lightning-card><br/>

        <lightning-card title="Navigate to Standard Home Tabs">
                <lightning-button-group>
                    <lightning-button label="Home Page" onclick={navigateToHomePage}></lightning-button>
                    <lightning-button label="Chatter Home Page" onclick={navigateToChatterHome}></lightning-button>
                </lightning-button-group>
        </lightning-card>
    </lightning-card>
</template>

navigationServiceLWCDemo.js
import { LightningElement, api } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';

export default class NavigationServiceLWCDemo extends NavigationMixin(LightningElement) {
    @api recordId;

    // Navigate to New Record Page
    navigateToNewRecordPage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                "recordId": this.recordId,
                "objectApiName": "Account",
                "actionName": "new"
            },
        });
    }

    // Navigate to Edit Record Page
    navigateToEditRecordPage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                "recordId": this.recordId,
                "objectApiName": "Account",
                "actionName": "edit"
            },
        });
    }

    // Navigate to view Record Page
    navigateToViewRecordPage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                "recordId": this.recordId,
                "objectApiName": "Account",
                "actionName": "view"
            },
        });
    }

    // Navigation to Account Recent List view
    navigateAccRecentView() {
        this[NavigationMixin.Navigate]({
            "type": "standard__objectPage",
            "attributes": {
                "objectApiName": "Account",
                "actionName": "list"
            },
            "state": {
                "filterName": "Recent"
            }
        });
    }

    // Navigation to Related list 
    navigateRelatedListView() {
        this[NavigationMixin.Navigate]({
            type: 'standard__recordRelationshipPage',
            attributes: {
                recordId: this.recordId,
                objectApiName: 'Account',
                relationshipApiName: 'Contacts',
                actionName: 'view'
            },
        });
    }

    // Navigation to Account object home page
    navigateAccObject() {
        this[NavigationMixin.Navigate]({
            "type": "standard__objectPage",
            "attributes": {
                "objectApiName": "Account",
                "actionName": "home"
            }
        });
    }

    // Navigation to contant object home page
    navigateConObject() {
        this[NavigationMixin.Navigate]({
            "type": "standard__objectPage",
            "attributes": {
                "objectApiName": "Contact",
                "actionName": "home"
            }
        });
    }

    // Navigation to lightning component
    navigateToComponent() {
        this[NavigationMixin.Navigate]({
            "type": "standard__component",
            "attributes": {
                "componentName": "c__NavigationDemoComponent"    
            }
        });
    }

    //  // Navigation to normal web page page
    navigateToWebPage() {
        this[NavigationMixin.Navigate]({
            "type": "standard__webPage",
            "attributes": {
                "url": "https://www.salesforcecodecrack.com/"
            }
        });
    }

     // Navigation to standard home page
    navigateToHomePage() {
        this[NavigationMixin.Navigate]({
            type: 'standard__namedPage',
            attributes: {
                pageName: 'home'
            },
        });
    }

    // Navigation to chatter home page
    navigateToChatterHome() {
        this[NavigationMixin.Navigate]({
            type: 'standard__namedPage',
            attributes: {
                pageName: 'chatter'
            },
        });
    }
}/span>

navigationServiceLWCDemo.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="navigationServiceLWCDemo">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Result:




Resources:
Navigation Service document
PageReference types

Wednesday, June 12, 2019

How to get sObject details using Record Id in Apex

Example:
Id idRec = '0060o00001GiaUGAAZ';
Schema.sObjectType sObjType = idRec.getSObjectType();
Schema.DescribeSObjectResult sObjDescribeResult = sObjType.getDescribe();

System.debug('Obj Name ===> '+sObjDescribeResult.getName());
System.debug('Obj key prefix ===> '+sObjDescribeResult.getKeyPrefix());
System.debug('Obj plural Name ===> '+sObjDescribeResult.getLabelPlural());

Result

Tuesday, June 11, 2019

How to render list of items in lightning web components(lwc)

This post explains how to render a list of items in lightning web components(lwc)

To render a list of items in Lightning web components(lwc), we use for:each or iterator directives.
 Add the directive to a nested <template> tag that encloses the HTML elements you want to repeat.
Note:
  • Regardless of which directive you use, you must use a key directive to assign a unique ID to each item. 
  • When a list changes, the framework uses the key to rerender only the item that changed.
  • The key in the template is used for performance optimization and isn’t reflected in the DOM at run time.

for:each

  • When using the for:each directive, use for:item="currentItem" to access the current item.
  • To assign a key to the first element in the nested template, use the key={uniqueId} directive.
  • you can use them for:index="index" to access the current item index, it is optional

Example:

forEachDirectiveDemo.html
<template>
    <lightning-card title="For Each Directive Demo" icon-name="custom:custom14">
        <ul class="slds-m-around_medium">
            <template for:each={cities} for:item="citiname">
                <li key={citiname.Id}>
                    <b style="color:cornflowerblue">{citiname.Name}</b>
                </li>
            </template>
        </ul>
    </lightning-card>
</template>

forEachDirectiveDemo.js
import { LightningElement, track } from 'lwc';

export default class ForEachDirectiveDemo extends LightningElement {
    @track cities = [
        {
            Id: 1,
            Name: 'Hyderabad',
        },
        {
            Id: 2,
            Name: 'Noida',
        },
        {
            Id: 3,
            Name: 'Pune',
        },
    ];
}
forEachDirectiveDemo.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="forEachDirectiveDemo">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Result:

iterator

If you want add special behavior to the first or last item in a list, use the iterator directive,
  iterator:iteratorName={array}. Use the iterator directive on a template tag.

Use iteratorName to access these properties:
  • value—The value of the item in the list. Use this property to access the properties of the array. For example, iteratorName.value.propertyName.
  • index—The index of the item in the list.
  • first—A boolean value indicating whether this item is the first item in the list.
  • last—A boolean value indicating whether this item is the last item in the list.

Example:

iteratorDirectiveDemo.html
<template>
    <lightning-card title="Iterator Directive Demo" icon-name="custom:custom14">
        <ul class="slds-m-around_medium">
            <template iterator:it={cities}>
                <li key={it.value.Id}>
                    <div if:true={it.first} style="border-top: 1px solid rgb(48, 17, 224);padding-top: 5px;"></div>
                    {it.value.Name}
                    <div if:true={it.last} style=" border-bottom: 1px solid rgb(209, 17, 17);padding-bottom: 5px;"></div>
                </li>
            </template>
        </ul>
    </lightning-card>
</template>
iteratorDirectiveDemo.js
import { LightningElement, track } from 'lwc';

export default class IteratorDirectiveDemo extends LightningElement {
    @track cities = [
        {
            Id: 1,
            Name: 'Hyderabad',
        },
        {
            Id: 2,
            Name: 'Noida',
        },
        {
            Id: 3,
            Name: 'Pune',
        },
    ];
}
Result:
Resource:
https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.create_lists

Monday, June 10, 2019

How to set Out of Office Message in Chatter in Salesforce Lightning Experience

This post explains how to set up out of office message in chatter.
This features only available in Lightning Experience.

Path:
From Setup, enter Chatter Settings in the Quick Find box, and then Select Chatter Settings
Check Users can set Out of Office messages and then save the settings.


Once you enable the Out Of Office message check box in chatter settings.
Go to your chatter profile click on Out Of Office button.



Enable Set Out of Office message Checkbox.
Enter start date, end date
Leave the message as it is, or customize it.



Once you save the Out of office message, it will show the message as like this.



That's it.

Resource:
https://help.salesforce.com/articleView?id=collab_admin_out_of_office.htm&type=5

Saturday, June 1, 2019

What is an Encrypted field in salesforce and its features


Encrypted Field in Salesforce

Encrypted Custom Fields allows users to store sensitive data in encrypted form and apply a mask when the data is displayed.
Ex: Password(********)

Who can see the data in the Encrypted field
  • Users who have the “View Encrypted Data” permission enabled on the profile will be able to view the field normally.
  • Users who do not have the “View Encrypted Data” permission on the profile will see the mask on data.
  • Users who have the “Modify All Data” permission on the profile will not be able to see the value of encrypted data fields.

Features of Encrypted Field
  • The field length is 175 characters in size.
  • We cannot be typecast as a Unique or External ID.
  • We can access the data of encrypted field in apex, i.e value is always unmasked.
  • We can not use the fields in SOQL where and order clauses.
  • We can not use the field in formula fields, workflow rules, workflow field updates, approval process entry criteria, and approval step criteria.
  •  we can not configure the field with a default value.