A blog about Salesforce CRM Configuration and development

Sunday, 30 December 2018

Send Email with Visualforce page (PDF) as an attachment using lightning component.


Sometimes Salesforce customer/user needs to send a Contract to their client and they don't want to make the Contract Terms document (pdf) manually by their employee. The Salesforce provides the standard object called Contract, which is related with Account and this Contract object keeps all the information about the Contract terms.  The customer/user wants to send an email to their client with Contracts records as an attached PDF document. In that case, we need to make the custom solution for this.

I am going to break this requirement into two part.
  1. The visualforce page which will show Contract details and it will render as PDF.
  2. The Lightning Component which will provide the user interface to enter required email fields. 
So, Let's jump into the 1st part of the requirement. The visualforce page will show the details of Contract.

ContractPDF.vfp
 <apex:page controller="ContractPDFCntrl" sidebar="false" showHeader="false" applyBodyTag="false" renderAs="PDF">  
   <html>  
     <head>  
     </head>  
     <body>  
       <table style="border-collapse: collapse; ">  
         <apex:repeat value="{!wrapperList}" var="row">  
           <tr style="border: 1px solid rgb(221, 219, 218); width:700px;">   
             <apex:repeat value="{!row.values}" var="value">  
               <td style="border: 1px solid rgb(221, 219, 218); padding: 15px; ">{!value}</td>  
               <td style="border: 1px solid rgb(221, 219, 218); padding: 15px;">  
                 {!row.values[value]}  
               </td>  
             </apex:repeat>  
           </tr>  
         </apex:repeat>  
       </table>  
     </body>  
   </html>  
 </apex:page>  
 renderAs="PDF"  
The renderAs="PDF" is an attribute of <apex:page> which is responsible for rendring the Visualforce page as pdf
Apex Controller: ContractPDFCntrl.apxc
 public class ContractPDFCntrl {  
   //WrapperList  
   public List<Wrapper> wrapperList{get;set;}  
   public ContractPDFCntrl(){  
     wrapperList = new List<Wrapper>();  
     // getting Contract record Id from page URL  
     string recordId = ApexPages.CurrentPage().getParameters().get('id');  
     Contract cont = [select id,Name,BillingAddress,Account.Name,CompanySignedId,CompanySignedDate,EndDate,ContractNumber,StartDate,  
              ContractTerm,CustomerSignedDate, CustomerSignedTitle,Description,ShippingAddress,SpecialTerms,  
              Status from Contract where id=:recordId] ;  
     for (Integer idx=0; idx<15; idx++)  
     {  
       wrapperList.add(new Wrapper());  
     }  
     if(cont.Name!=null){  
       wrapperList[0].addValue('Name',cont.Name);  
     }  
     else{  
       wrapperList[0].addValue('Name','N/A');  
     }  
     if(cont.BillingAddress!=null){  
       Address addr = cont.BillingAddress;  
       wrapperList[1].addValue('Billing Street',addr.getStreet());  
     }else{  
       wrapperList[1].addValue('Billing Street','N/A');  
     }  
     if(Account.Name!=null ){  
       wrapperList[2].addValue('Name',cont.Account.Name);  
     }else{  
       wrapperList[2].addValue('Name','N/A');  
     }  
     if(cont.CompanySignedId !=null){  
       wrapperList[3].addValue('Company Signed Id',cont.CompanySignedId);  
     }else{  
       wrapperList[3].addValue('Company Signed Id','N/A');  
     }  
     if(cont.CompanySignedDate !=null){  
       wrapperList[4].addValue('Company SignedDate',String.valueOf(cont.CompanySignedDate));  
     }else{  
       wrapperList[4].addValue('Company SignedDate','N/A');  
     }  
     if(cont.EndDate !=null){  
       wrapperList[5].addValue('EndDate',String.valueOf(cont.EndDate));  
     }else{  
       wrapperList[5].addValue('EndDate','N/A');  
     }  
     if(cont.ContractNumber !=null){  
       wrapperList[6].addValue('Contract Number',String.valueOf(cont.ContractNumber));  
     }else{  
       wrapperList[6].addValue('Contract Number','N/A');  
     }  
     if(cont.StartDate !=null){  
       wrapperList[7].addValue('StartDate',String.valueOf(cont.StartDate));  
     }else{  
       wrapperList[7].addValue('StartDate','N/A');  
     }  
     if(cont.ContractTerm !=null){  
       wrapperList[8].addValue('ContractTerm',String.valueOf(cont.ContractTerm));  
     }else{  
       wrapperList[8].addValue('ContractTerm','N/A');  
     }  
     if(cont.CustomerSignedDate !=null){  
       wrapperList[9].addValue('CustomerSignedDate',String.valueOf(cont.CustomerSignedDate));  
     }else{  
       wrapperList[9].addValue('CustomerSignedDate','N/A');  
     }  
     if(cont.CustomerSignedTitle !=null){  
       wrapperList[10].addValue('CustomerSignedTitle',String.valueOf(cont.CustomerSignedTitle));  
     }else{  
       wrapperList[10].addValue('CustomerSignedTitle','N/A');  
     }  
     if(cont.Description !=null){  
       wrapperList[11].addValue('Description',String.valueOf(cont.Description));  
     }else{  
       wrapperList[11].addValue('Description','N/A');  
     }  
     if(cont.ShippingAddress !=null){  
       Address addr = cont.ShippingAddress;  
       wrapperList[12].addValue('ShippingAddress',String.valueOf(addr.getStreet()));  
     }else{  
       wrapperList[12].addValue('ShippingAddress','N/A');  
     }  
     if(cont.SpecialTerms !=null){  
       wrapperList[13].addValue('SpecialTerms',String.valueOf(cont.SpecialTerms));  
     }else{  
       wrapperList[13].addValue('SpecialTerms','N/A');  
     }  
     if(cont.Status !=null){  
       wrapperList[14].addValue('Status',String.valueOf(cont.Status));  
     }else{  
       wrapperList[14].addValue('Status','N/A');  
     }  
   }  
   //Wrapper class  
   public class Wrapper{  
     public Map<String,string> values {get; set;}  
     // constructor  
     public Wrapper()  
     {  
       values = new Map<String,String>();  
     }  
     //Wrapper method  
     public void addValue(String Name, String Value)  
     {     //put data in values map.  
       values.put(Name,Value);  
     }  
   }  
 }  

When you preview the page it will look like the screen below.

https://yourdomain-dev-ed--c.ap7.visual.force.com/apex/ContractPDF?id=ContractId

2nd Part: Lightning Component.
SendEmailComponent.cmp
 <aura:component controller="SendEmailCntrl" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >  
   <aura:handler name="init" value="{!this}" action="{!c.OnLoad}"/>  
   <!-- attributes-->   
   <aura:attribute name="email" type="string"/>  
   <aura:attribute name="subject" type="string"/>  
   <aura:attribute name="body" type="string"/>  
   <aura:attribute name="cc" type="string"/>  
   <aura:attribute name="Bcc" type="string"/>  
   <aura:attribute name="mailStatus" type="boolean" default="false"/>  
   <!-- Account record Id attaribute -->  
   <aura:attribute name="recordId" type="string"/>  
   <!-- Wrapper -->  
   <aura:attribute name="WrapObj" type="wrapper"/>  
   <!-- User Info -->  
   <aura:attribute name="currentUser" type="user"/>  
   <!-- CC input TRUE?FALSE Boolean -->  
   <aura:attribute name="displayCC" type="Boolean" default="false"/>  
   <div class="slds-m-around--medium">  
     <div class="slds-container--medium">  
       <div class="slds-form--stacked">  
         <div class="slds-form-element">  
           <label class="slds-form-element__label" for="CC">From</label>  
           <div class="slds-form-element__control">  
             <ui:inputEmail class="slds-input" aura:id="email" value="{!v.currentUser.Email}" required="true" placeholder="abc@email.com" disabled="true"/>  
           </div>  
         </div>  
         <div class="slds-form-element">  
           <label class="slds-form-element__label" for="CC">To</label>  
           <div class="slds-form-element__control">  
             <ui:inputEmail class="slds-input" aura:id="email" value="{!v.email}" required="true" placeholder="abc@email.com"/>  
             &nbsp;<a onclick="{!c.showCC}">CC</a>  
           </div>  
         </div>  
         <aura:if isTrue="{!v.displayCC}">  
           <div class="slds-form-element">  
             <label class="slds-form-element__label" for="CC">Cc</label>  
             <div class="slds-form-element__control">  
               <ui:inputEmail class="slds-input" aura:id="cc" value="{!v.cc}" placeholder="abc@email.com"/>  
             </div>  
           </div>  
         </aura:if>  
         <div class="slds-form-element">  
           <label class="slds-form-element__label" for="CC">Bcc</label>  
           <div class="slds-form-element__control">  
             <ui:inputEmail class="slds-input" aura:id="Bcc" value="{!v.Bcc}" placeholder="abc@email.com"/>  
           </div>  
         </div>  
         <div class="slds-form-element">  
           <label class="slds-form-element__label" for="CC">Subject</label>  
           <div class="slds-form-element__control">  
             <ui:inputText class="slds-input" aura:id="subject" value="{!v.subject}" placeholder="Subject"/>  
           </div>  
         </div>  
         <div class="slds-form-element">  
           <label class="slds-form-element__label" for="textareaSample2">Mail Body</label>  
           <div class="slds-form-element__control">  
             <lightning:inputRichText aura:id="body" value="{!v.body}" />  
           </div>  
         </div>  
         <div class="slds-page-header">  
           Attach Contract  
         </div>  
         <table class="slds-table slds-table--bordered" style="border-right: 1px solid rgb(216, 221, 230); border-left: 1px solid rgb(216, 221, 230);border-top: 0px solid rgb(216, 221, 230);">  
           <thead>  
             <tr>  
               <th>Select</th>  
               <th>Contract Number</th>  
             </tr>  
           </thead>  
           <tbody>  
             <aura:iteration items="{!v.WrapObj}" var="w">  
               <tr>  
                 <td><ui:inputCheckbox class="myCheckbox" aura:id="check" value="{!w.check}"/></td>  
                 <td>{!w.contrct.ContractNumber}</td>  
               </tr>  
             </aura:iteration>  
           </tbody>  
         </table>  
         <br/><br/>  
         <button type="button" class="slds-button slds-button--brand" onclick="{!c.sendMail}">Send</button>  
       </div>  
     </div>  
   </div>  
   <!-- End -->  
 </aura:component>  
SendEmailComponentController.js
 ({  
   OnLoad : function(component,event,helper){  
     helper.getCurrentUser(component,event,helper);  
     var action = component.get("c.onLoad");  
     action.setParams({  
       "recordId" : component.get("v.recordId")  
     });  
     action.setCallback(this,function(response){  
       var state = response.getState();  
       if(state == 'SUCCESS'){  
         var result = response.getReturnValue();  
         console.log('Result : ### ' + JSON.stringify(result));  
         component.set("v.WrapObj", result);  
       }else{  
         console.log('something bad happed! ');  
       }  
     });  
     $A.enqueueAction(action);  
   },  
   sendMail: function(component, event, helper) {  
     //alert('**** Test');  
     var wrappers = component.get('v.WrapObj');  
     var ids=new Array();  
     alert('array size: ' +wrappers.length);  
     for (var idx=0; idx<wrappers.length; idx++) {  
       alert(wrappers[idx].check);  
       if (wrappers[idx].check) {  
         ids.push(wrappers[idx].contrct.Id);  
       }  
     }  
     var idListJSON=JSON.stringify(ids);  
     //alert(idListJSON);  
     var Email = component.get("v.email");  
     var Subject = component.get("v.subject");  
     var body = component.get("v.body");  
     var CcEmail = component.get("v.cc");  
     var BcccEmail = component.get("v.Bcc");    
     if ($A.util.isEmpty(Email) || !Email.includes("@")) {  
       alert('Please Enter valid Email Address');  
     } else {  
       helper.sendEmailHelper(component, Email, Subject, body, idListJSON,CcEmail,BcccEmail);  
     }  
   },  
   //Show CC email input  
   showCC : function(component,event,helper){  
     component.set("v.displayCC", true);  
   },  
   closeModal : function(component,event,helper){  
     //Close the modal  
     component.set("v.isOpen",false);  
   },  
 })  
SendEmailComponentHelper.js
 ({  
   sendEmailHelper: function(component, getEmail, getSubject, getbody,getContractIds,getCcEmail,getBcccEmail) {  
     // call the server side controller method to send email  
     var action = component.get("c.sendContractMail");  
     action.setParams({  
       'emailId': getEmail,  
       'subject': getSubject,  
       'emailbody': getbody,  
       'ContractIds' :getContractIds,  
       'ccEmail' : getCcEmail,  
       'bccEmail' : getBcccEmail  
     });  
     action.setCallback(this, function(response) {  
       var state = response.getState();  
       if (state === "SUCCESS") {  
         var storeResponse = response.getReturnValue();  
         //If server response return 'success' show toast message that email has been sent.  
         if(storeResponse == 'success'){  
           var toastEvent = $A.get("e.force:showToast");  
           toastEvent.setParams({  
             "title": "Success!",  
             "type": "success",  
             "message": "Email Sent Successfully!"  
           });  
           toastEvent.fire();  
           var navEvt = $A.get("e.force:navigateToSObject");  
           navEvt.setParams({  
             "recordId": component.get("v.recordId"),  
             "slideDevName": "detail"  
           });  
           navEvt.fire();  
         }  
         else{ // show error toast message  
           var toastEvent = $A.get("e.force:showToast");  
           toastEvent.setParams({  
             "title": "Success!",  
             "type": "error",  
             "message": storeResponse  
           });  
           toastEvent.fire();  
         }  
       }  
     });  
     // calling server side Apex method(This will be called asynchronously).  
     $A.enqueueAction(action);  
   },  
   getCurrentUser : function(component,event,helper){  
     var action = component.get("c.fetchUserDetails");  
     action.setCallback(this,function(response){  
       var state = response.getState();  
       if(state == 'SUCCESS'){  
         var result = response.getReturnValue();  
         console.log('result: ' + result);  
         component.set("v.currentUser", result);  
       }  
       else{  
         console.log('Something bad happend! ');  
       }  
     });  
     // calling sever side action method  
     $A.enqueueAction(action);  
   }  
 })  
Apex Controller:
 public class SendEmailCntrl {  
   @AuraEnabled  
   public static List<Wrapper> onLoad(string recordId){  
     List<Wrapper> wrapList = new List<Wrapper>();  
     Account acc = [select id,(select id,Name,ContractNumber from Contracts) from Account where id=:recordId];  
     for(Contract c: acc.contracts){  
       wrapList.add(new Wrapper(false,c));  
     }  
     //Return wrapperList  
     return wrapList;  
   }  
   @AuraEnabled   
   public static user fetchUserDetails(){  
     // query current user information   
     User oUser = [select id,Name,TimeZoneSidKey,Username,Alias,Country,Email,FirstName,LastName,IsActive   
            FROM User Where id =: userInfo.getUserId()];  
     return oUser;  
   }  
   @AuraEnabled   
   public static String sendContractMail(String emailId ,String subject ,String emailbody,String ContractIds, String ccEmail, String bccEmail){  
     system.debug('ContractIds: ' +ContractIds);  
     String SUCCESS_MSG = '';  
     String ERROR_MSG = '';  
     List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();     
     // Create mail obj  
     Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();  
     // list of people to whome email will be send  
     List<String> sendTo = emailId.split(',');  
     //sendTo.add(mMail);  
     List<String> ccTo = new List<String>();  
     if(ccEmail!='' && ccEmail!=null){  
       ccTo = ccEmail.split(',');  
     }  
     List<String> bccTo = new List<String>();  
     if(bccEmail!='' && bccEmail!=null){  
       bccTo = bccEmail.split(',');  
     }  
     // Send To    
     mail.setToAddresses(sendTo);  
     // bcc to  
     if(bccTo.size()>0)  
       mail.setBccAddresses(bccTo);  
     //cc to   
     if(ccTo.size()>0)  
       mail.setCcAddresses(ccTo);  
     //Set who the email is sent from  
     mail.setReplyTo('noreply@gmail.com'); // change it with your mail address.  
     mail.setSenderDisplayName('salesforce User');   
     // subject and email body  
     mail.setSubject(subject);  
     mail.setHtmlBody(emailbody);  
     // add email in list  
     mails.add(mail);  
     Type idArrType=Type.forName('List<Id>');  
     // selected contarctIds  
     List<Id> ids=(List<Id>) JSON.deserialize(ContractIds, idArrType);  
     //List of Emailfileattachment  
     List<Messaging.Emailfileattachment> fileAttachments = new List<Messaging.Emailfileattachment>();  
     // Map<Contract, Blob>   
     Map<Contract, Blob> bodyMap = new Map<Contract, Blob>();  
     //Attachments  
     for(Contract c: [select id,ContractNumber from Contract where id IN: ids]){  
       PageReference pdf = Page.ContractPDF;//ContractPDF is the name of vf page  
       pdf.getParameters().put('id',c.Id); //sending contract id paramater to 'ContractPDF' VF.  
       bodyMap.put(c, pdf.getContent()); // get vf page as blob.  
     }  
     for(Contract cntrct: bodyMap.keySet()){  
       // Add to attachment file list  
       Messaging.Emailfileattachment efa = new Messaging.Emailfileattachment();  
       efa.setFileName(cntrct.ContractNumber);  
       if(bodyMap.containsKey(cntrct)){  
         Blob b = bodyMap.get(cntrct);  
         efa.setBody(b);  
       }  
       efa.setContentType('application/pdf');  
       fileAttachments.add(efa);  
     }  
     mail.setFileAttachments(fileAttachments);  
     //Send email  
     try{  
       Messaging.sendEmail(mails);  
       SUCCESS_MSG = 'success';  
       return SUCCESS_MSG;  
     }  
     catch(Exception e){  
       ERROR_MSG = 'Error:'+e.getmessage();  
       return ERROR_MSG;  
     }  
   }   
 }  
Now create Quick Action button on Account.
Add this Quick Action on Account page Layout.
Output:



Hope this post will help many.
Thanks
Arun Kumar
Share:

Saturday, 22 December 2018

Search Metadata In Lightning Experience



Hello,

I believe every Salesforce developer try to access/find the Apex Class, Apex Trigger, Visualforce page, Visualforce component, etc. while developing in Salesforce, we have to navigate to the separate page to access these items. Let's suppose if it is available in one place and can search these items that will save our time. Isn't it?. 

In Salesforce classic you may have used 'Salesforce Advance Code Searcher' google chrome extension, this makes the things super easy in Salesforce classic but it is not available for Lightning Experience till now, so I thought there should be something there (In Lightning Experience) to search small Metadata information like Apex Classes, Apex Triggers, Visualforce pages, Visualforce components, Static resources, etc. in single place.

I have developed a lightning component which will search this information in a place.


Find the full code here.

SearchMetadata Cmp: SearchMetadata JS: SearchMetadata Helper: SearchMetadataCss: searchMetadataCntrl: UtilsClass: SessionIdPage:
Share:

Saturday, 13 October 2018

Upload multiple files through Visualforce page

Upload Multiple files with the progress bar.



While uploading the multiple files at the same time you may have faced the problem like 'View state size limit exceeded', 'Heap size' limit and file size limit. To overcome these limits we can use javascript to handle the file uploading through the Visualforce page.


I have also faced the problem like View state size limit exceeded and Heap size limit, to solve the issue I decided to send/upload the files using HTTP POST Request with the help of Ajax, Jquery, javascript. Below is the code snippet for uploading files in the ContentVersion object.

 <apex:page>  
   <html>  
     <head>  
       <apex:slds />  
       <apex:includeScript value="https://code.jquery.com/jquery-2.2.4.js"/>  
       <script>  
       //function starts  
       $(function(){  
         var fileLenght=0;  
         $('#uploadBtn').click(function() {  
           var v=document.getElementById('addr');  
           console.log(v.files.length);  
           fileLenght = v.files.length;  
           for (var i = 0; i < v.files.length; i++) {  
             uploadSelectedFile(v.files[i], function(err, res){   
               FileUploading += 1;  
               if (FileUploading === FileUploaded){  
                 alert('upload completed');  
                 //blank input file value   
                 document.getElementById("addr").value = "";  
               }  
             })  
           }   
         });  
         var FileUploading = 0;  
         var FileUploaded = 0;  
         var ids = new Array();  
         var uploadSelectedFile = function(file, callback) {  
           filetoBase64(file, function(err, content){  
             var conVer_object = {  
               ContentLocation : 'S',  
               VersionData : content,   
               PathOnClient : file.name,   
               Title : file.name   
             };  
             $.ajax({  
               url: '/services/data/v39.0/sobjects/ContentVersion',  
               data: JSON.stringify(conVer_object ),  
               type: 'POST',  
               processData: false,  
               contentType: false,  
               headers: {'Authorization': 'Bearer {!$Api.Session_ID}', 'Content-Type': 'application/json'},  
               xhr: function(){  
                 var xhr = new window.XMLHttpRequest();  
                 //Upload progress  
                 xhr.upload.addEventListener("progress", function(evt){  
                   if (evt.lengthComputable) {  
                     $('#progress_bar_container').css('display', 'block');  
                     var percentComplete = evt.loaded / evt.total;  
                     console.log('percentComplete '+percentComplete );  
                     var percentCompletex= percentComplete*100;  
                     $('#percentText').html("Uploading. Please wait... "+Math.round(percentCompletex)+"%");  
                     $('.progress').css('width', percentCompletex+ "%");  
                     if(percentCompletex == 100){  
                       $('#progress_bar_container').css('display', 'none');  
                     }  
                   }  
                 }, false);  
                 return xhr;  
               },  
               success: function(response) {  
                 FileUploaded += 1;  
                 console.log(response.id); // the id of the attachment  
                 ids.push(response.id);   
                 console.log('Ids: ' +ids);  
                 $('#records').html('File has been uploeded. Uploaded File ids: ' +ids);  
                 if(fileLenght == FileUploaded ){   
                   //calculateLocation(ids.toString());  
                 }  
                 callback(null, true)  
               },  
             });  
           });  
         }  
         //Read file  
         var filetoBase64 = function(file, callback){  
           var reader = new FileReader();  
           reader.onload = function() {  
             var myFileContents = reader.result;  
             var base64Mark = 'base64,';  
             var dataStart = myFileContents.indexOf(base64Mark) + base64Mark.length;  
             myFileContents = myFileContents.substring(dataStart);  
             callback(null, myFileContents);  
           }  
           reader.readAsDataURL(file);  
         }  
         });  
       </script>  
       <style>  
         .slds-scope .slds-page-header{  
         border-radius: 0px;  
         box-shadow: 0 0px 0px 0 rgba(0, 0, 0, 0.1);  
         }  
         .bodyPart{  
         padding:10px;  
         }  
       </style>  
     </head>  
     <body>  
       <apex:form >  
         <div class="slds" style="border:1px solid #D9D9D9;">  
           <div class="slds-page-header">  
             <span style=""> Upload Files</span>  
           </div>  
           <div id="progress_bar_container" style="display:none; padding:10px;">  
             <span id="percentText"></span>  
             <div class="slds-progress-bar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="25" role="progressbar">  
               <span class="slds-progress-bar__value progress" style=" width: 0%;">  
                 <span class="slds-assistive-text">Progress: 25%</span>  
               </span>  
             </div>  
           </div><br/>  
           <div class="bodyPart">  
             Select file: <input type="file" name="files[]" multiple="multiple" id="addr"/>  
             <input type="button" id="uploadBtn" name="Address" value="Upload" class="slds-button slds-button--brand"></input>  
         </div>  
         <div id="uploadedrec" style="padding:10px;">  
           <span id="records" style=" color:green; ">  
           </span>  
         </div>  
       </div>  
     </apex:form>  
   </body>  
 </html>  
 </apex:page>  


On 'Upload' button click this method will be called. Here '#uploadBtn' is the id of the button.
  $('#uploadBtn').click(function() { ...}  
If the user selects multiple files, Iterating over files length.
 for (var i = 0; i < v.files.length; i++) { ... }  
In every iteration uploadSelectedFile, JS function will be called.
  uploadSelectedFile(v.files[i], function(err, res){ ...}  
Reading the files and converted into the base64 format.
 filetoBase64(file, function(err, content){ }  
ContentVersion object variable with required fields.
 var conVer_object = {   
         ContentLocation : 'S',   
         VersionData : content,    // File body
         PathOnClient : file.name, // File name   
         Title : file.name    //File title
        };   
This AJAX method used to perform the asynchronous HTTP request to Salesforce.
 $.ajax({ ... }  
URL or Endpoint: HTTP POST method will post the content to the specified location (ContentVersion object).
 url: '/services/data/v39.0/sobjects/ContentVersion',  
Header: Salesforce Session_ID used for authentication.
  headers: {'Authorization': 'Bearer {!$Api.Session_ID}', 'Content-Type': 'application/json'},   
XML HttpRequest object: Send the data from page to server (Salesforce) in the background.
 var xhr = new window.XMLHttpRequest();  
The addEventListener function listen to the progress of uploading.
  xhr.upload.addEventListener("progress", function(evt){ ... }  
These lines of code will display the Progress bar on the page.
 $('#progress_bar_container').css('display', 'block');   
 var percentComplete = evt.loaded / evt.total;   
 console.log('percentComplete '+percentComplete );   
 var percentCompletex= percentComplete*100;   
 $('#percentText').html("Uploading. Please wait... "+Math.round(percentCompletex)+"%");   
 $('.progress').css('width', percentCompletex+ "%");   
 if(percentCompletex == 100){   
    $('#progress_bar_container').css('display', 'none');   
 }  

This success function will return the success message with the ids of the file that just uploaded.
  success: function(response) { ... }  
Output:





References:
XML Http Request
AJAX Method
ContentVersion

Hope this blog post will help many.
Happy coding...
Share:

Saturday, 6 October 2018

How to get Report data in Apex

Sometimes we need to get the report data in Apex to present it on custom report charts. We can get the report data with the help of 'Reports.ReportResults' and 'Reports.ReportManager' class of Apex.
ReportResults: This Apex class contains the results of running a report.
ReportManager: With the help of this Apex class we can run a report synchronously or asynchronously through Apex.
 public class ReportsData{  
   public static String getreport(string reportId){    
     // Get the Report data    
     Reports.ReportResults reportReturned =Reports.ReportManager.runReport(reportId, true);    
     system.debug('Report Data: '+JSON.serialize(reportReturned));    
     //Return Report data in JSON serialize format.    
     return JSON.serialize(reportReturned);    
   }    
 }   
This line of code runs the report of specified report id and return the result.
 Reports.ReportResults reportReturned =Reports.ReportManager.runReport(reportId, true);   
Here the runReport(Id reportId, Boolean includeDetails) method takes two parameter reportId and a boolean value True or false.
 runReport(reportId, true)  
JSON is an Apex class which contains methods for serializing Object into JSON format. JSON.serialize(reportReturned) method serialize the object into JSON format.
 JSON.serialize(reportReturned)  
Calling getreport('id') method. The result (report data) will return in JSON format.
 String result = ReportsData.getreport('reportId');  
Output: Parse the result according to needs.
 {  
  "reportMetadata": {  
   "topRows": null,  
   "standardFilters": null,  
   "standardDateFilter": {  
    "startDate": null,  
    "endDate": null,  
    "durationValue": "CUSTOM",  
    "column": "CUST_CREATED_DATE"  
   },  
   "sortBy": null,  
   "showSubtotals": true,  
   "showGrandTotal": true,  
   "scope": "organization",  
   "reportType": {  
    "type": "CustomEntity$Job_Applications__c@Job_Applications__c.Positions__c",  
    "label": "Job Applications with Positions"  
   },  
   "reportFormat": "SUMMARY",  
   "reportFilters": null,  
   "reportBooleanFilter": null,  
   "name": "Job Applications by Functional Area",  
   "id": "00O28000004naGqEAI",  
   "historicalSnapshotDates": null,  
   "hasRecordCount": true,  
   "hasDetailRows": true,  
   "groupingsDown": [  
    {  
     "sortOrder": "ASCENDING",  
     "sortAggregate": null,  
     "name": "FK_Positions__c.Functional_Area__c",  
     "dateGranularity": "NONE"  
    },  
    {  
     "sortOrder": "ASCENDING",  
     "sortAggregate": null,  
     "name": "FK_NAME",  
     "dateGranularity": "NONE"  
    }  
   ],  
   "groupingsAcross": null,  
   "division": null,  
   "developerName": "Job_Applications_by_Functional_Area",  
   "detailColumns": [  
    "CUST_NAME",  
    "Job_Applications__c.Status__c"  
   ],  
   "description": null,  
   "customSummaryFormula": null,  
   "currencyCode": null,  
   "crossFilters": null,  
   "buckets": null,  
   "aggregates": [  
    "RowCount"  
   ]  
  },  
  "reportExtendedMetadata": {  
   "groupingColumnInfo": {  
    "FK_NAME": {  
     "name": "FK_NAME",  
     "label": "Positions: Position Name",  
     "groupingLevel": 1,  
     "dataType": "STRING_DATA"  
    },  
    "FK_Positions__c.Functional_Area__c": {  
     "name": "FK_Positions__c.Functional_Area__c",  
     "label": "Positions: Functional Area",  
     "groupingLevel": 0,  
     "dataType": "PICKLIST_DATA"  
    }  
   },  
   "detailColumnInfo": {  
    "Job_Applications__c.Status__c": {  
     "name": "Job_Applications__c.Status__c",  
     "label": "Status",  
     "dataType": "PICKLIST_DATA"  
    },  
    "CUST_NAME": {  
     "name": "CUST_NAME",  
     "label": "Job Applications: Job Applications Name",  
     "dataType": "STRING_DATA"  
    }  
   },  
   "aggregateColumnInfo": {  
    "RowCount": {  
     "name": "RowCount",  
     "label": "Record Count",  
     "downGroupingContext": null,  
     "dataType": "INT_DATA",  
     "acrossGroupingContext": null  
    }  
   }  
  },  
  "hasDetailRows": true,  
  "groupingsDown": {  
   "groupings": [  
    {  
     "value": "Human Resources",  
     "label": "Human Resources",  
     "key": "0",  
     "groupings": [  
      {  
       "value": "a0K28000001Bm00EAC",  
       "label": "Sr. Benefits Specialist",  
       "key": "0_0",  
       "groupings": null  
      }  
     ]  
    },  
    {  
     "value": "Information Technology",  
     "label": "Information Technology",  
     "key": "1",  
     "groupings": [  
      {  
       "value": "a0K28000001BjefEAC",  
       "label": "Software Engineer",  
       "key": "1_0",  
       "groupings": null  
      },  
      {  
       "value": "a0K28000001Bm01EAC",  
       "label": "Sr. UI Designer",  
       "key": "1_1",  
       "groupings": null  
      }  
     ]  
    },  
    {  
     "value": "Miscellaneous",  
     "label": "Miscellaneous",  
     "key": "2",  
     "groupings": [  
      {  
       "value": "a0K28000001Bl4MEAS",  
       "label": "Benefits Specialist",  
       "key": "2_0",  
       "groupings": null  
      }  
     ]  
    }  
   ]  
  },  
  "groupingsAcross": {  
   "groupings": null  
  },  
  "factMap": {  
   "1_0!T": {  
    "key": "1_0!T",  
    "aggregates": [  
     {  
      "value": 1,  
      "label": "1"  
     }  
    ],  
    "rows": [  
     {  
      "dataCells": [  
       {  
        "value": "a0O280000003iHPEAY",  
        "label": "JA-00001"  
       },  
       {  
        "value": "New",  
        "label": "New"  
       }  
      ]  
     }  
    ]  
   },  
   "2_0!T": {  
    "key": "2_0!T",  
    "aggregates": [  
     {  
      "value": 1,  
      "label": "1"  
     }  
    ],  
    "rows": [  
     {  
      "dataCells": [  
       {  
        "value": "a0O280000003ib8EAA",  
        "label": "JA-00002"  
       },  
       {  
        "value": "Review Resume",  
        "label": "Review Resume"  
       }  
      ]  
     }  
    ]  
   },  
   "1_1!T": {  
    "key": "1_1!T",  
    "aggregates": [  
     {  
      "value": 1,  
      "label": "1"  
     }  
    ],  
    "rows": [  
     {  
      "dataCells": [  
       {  
        "value": "a0O280000019ULPEA2",  
        "label": "JA-00004"  
       },  
       {  
        "value": "Hired",  
        "label": "Hired"  
       }  
      ]  
     }  
    ]  
   },  
   "T!T": {  
    "key": "T!T",  
    "aggregates": [  
     {  
      "value": 5,  
      "label": "5"  
     }  
    ],  
    "rows": null  
   },  
   "2!T": {  
    "key": "2!T",  
    "aggregates": [  
     {  
      "value": 1,  
      "label": "1"  
     }  
    ],  
    "rows": null  
   },  
   "1!T": {  
    "key": "1!T",  
    "aggregates": [  
     {  
      "value": 2,  
      "label": "2"  
     }  
    ],  
    "rows": null  
   },  
   "0_0!T": {  
    "key": "0_0!T",  
    "aggregates": [  
     {  
      "value": 2,  
      "label": "2"  
     }  
    ],  
    "rows": [  
     {  
      "dataCells": [  
       {  
        "value": "a0O280000019USBEA2",  
        "label": "JA-00005"  
       },  
       {  
        "value": "Review Resume",  
        "label": "Review Resume"  
       }  
      ]  
     },  
     {  
      "dataCells": [  
       {  
        "value": "a0O280000003jMBEAY",  
        "label": "JA-00003"  
       },  
       {  
        "value": "Phone Screen",  
        "label": "Phone Screen"  
       }  
      ]  
     }  
    ]  
   },  
   "0!T": {  
    "key": "0!T",  
    "aggregates": [  
     {  
      "value": 2,  
      "label": "2"  
     }  
    ],  
    "rows": null  
   }  
  },  
  "allData": true  
 }  
References:
ReportManager
ReportResults
Share:

Monday, 1 October 2018

Recent Items Lightning Component

Recent Items Lightning Component:



I believe every Salesforce developer try to access the recently used items in the Salesforce org, In Lightning experience user interface we have to click on 'Setting' icon and then need to click on 'Set Up' after that we get the Set-Up home page, on this page (standard), recently used items displayed. But there is a limit only 10 items displayed on this page and also we can not filter the record based on types like if we want to display recent used Apex classes, Visualforce Pages, Triggers, Custom object, Custom fields, Users, Static resource, etc.  If someone wants to download the log of the recently used items they cannot do using standard page.

I also try to find the recently used items most of the times, I think there should be more than 10 recent items on the recent items page, and also there should be the filter to filter the records. The filter will make the user easy to find the items that they wanted to find.

With these ideas in mind, I have developed a Lightning component which will display up to 100 recently used items on the component, each page will show 10 items, for next items, there are pagination buttons on the component, To filter the record based on types there is a picklist which will have all the possible types to filter the records. There is a button called 'Download As Csv' after clicking on this button the recently used items will be downloaded in the '.CSV' format. 

This component can be used on the Home Page, Detail page, Lightning Tab. etc

To use this component you have to add your salesforce domain URL in remote site setting: e.g:- https://your_domain -dev-ed.my.salesforce.com/

You can install this Lightning component from here.

https://allrecentitem-developer-edition.na59.force.com/

Share:

Saturday, 22 September 2018

Lightning - File upload with title using "lightning:fileUpload"

Lightning File Uploader Component:  <lightning:fileUpload />

I am writing this blog post just because, I faced a problem while working with the standard file uploader component, the problem is when you upload the file using standard component there is no way to enter the file Name or Title, it automatically get the file name and updates the Title with that file name, but sometimes users want to enter the specific Name or Title in that case we can not do that with standard component.


The first thing comes to my mind to solve this problem is if anyhow I get the files details before upload then I can change the file Title with the user entered name but no luck, there is no action defined for before upload in lightning:fileUpload documentation.

But, lightning:fileUpload component provides 'onuploadfinished' (The action triggered when files have finished uploading.) action, we can call the action (Method) after the file uploading finished and can get the details of files that just uploaded, after that, we can change the Title of File based on the user entered Title.

Below are the Lightning component codes that I have created to solve the problem that I was facing.

Component:
 <aura:component controller="SimplyfyFilesCntrl" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >  
   <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>  
   <aura:attribute name="files" type="ContentDocument[]"/>  
   <aura:attribute name="recordId" type="string"/>  
   <aura:attribute name="accept" type="List" default="['.jpg', '.jpeg','.pdf','.csv','.xlsx']"/>  
   <aura:attribute name="multiple" type="Boolean" default="true"/>  
   <aura:attribute name="disabled" type="Boolean" default="false"/>  
   <div class="slds">  
     <div style="border-left: 1px solid rgb(221, 219, 218);  
           border-right: 1px solid rgb(221, 219, 218);  
           border-bottom: 1px solid rgb(221, 219, 218);  
           border-top: 1px solid rgb(221, 219, 218);">  
       <div class="slds-page-header" style="border-radius: 0px; border-right: 0px;border-left: 0px;border-top: 0px;  
                          box-shadow: 0 0px 0px 0 rgba(0, 0, 0, 0.1);">  
         Files  
       </div>  
       <div class="slds-grid">  
         <div class="slds-col slds-size--5-of-12">  
           <lightning:input type="text" name="input1" label="Enter File Name" aura:id="fileName" />  
         </div>&nbsp; &nbsp;  
         <div class="slds-col slds-size---of-12">  
           <lightning:fileUpload label="" multiple="{!v.multiple}"   
                      accept="{!v.accept}" recordId="{!v.recordId}"   
                      onuploadfinished="{!c.UploadFinished}" />  
         </div>  
       </div><br/>  
       <table class="slds-table slds-table--bordered">  
         <thead>  
           <tr>  
             <th>Title</th>  
             <th>FileType</th>  
             <th>Description</th>  
           </tr>  
         </thead>  
         <tbody>  
           <aura:iteration items="{!v.files}" var="f">  
             <tr>  
               <td> <a href="" id="{!f.Id}" onclick="{!c.OpenFile}">{!f.Title}</a></td>  
               <td>{!f.FileType}</td>  
               <td>{!f.Description}</td>  
             </tr>  
           </aura:iteration>  
         </tbody>  
       </table>  
     </div>  
   </div>  
 </aura:component>  

JS Controller:
 ({  
   doInit:function(component,event,helper){  
     var action = component.get("c.getFiles");  
     action.setParams({  
       "recordId":component.get("v.recordId")  
     });      
     action.setCallback(this,function(response){  
       var state = response.getState();  
       if(state=='SUCCESS'){  
         var result = response.getReturnValue();  
         console.log('result: ' +result);  
         component.set("v.files",result);  
       }  
     });  
     $A.enqueueAction(action);  
   } ,  
   //Open File onclick event  
   OpenFile :function(component,event,helper){  
     var rec_id = event.currentTarget.id;  
     $A.get('e.lightning:openFiles').fire({ //Lightning Openfiles event  
       recordIds: [rec_id] //file id  
     });  
   },  
   UploadFinished : function(component, event, helper) {  
     var uploadedFiles = event.getParam("files");  
     var documentId = uploadedFiles[0].documentId;  
     var fileName = uploadedFiles[0].name;  
     helper.UpdateDocument(component,event,documentId);  
     var toastEvent = $A.get("e.force:showToast");  
     toastEvent.setParams({  
       "title": "Success!",  
       "message": "File "+fileName+" Uploaded successfully."  
     });  
     toastEvent.fire();  
     /* Open File after upload  
     $A.get('e.lightning:openFiles').fire({  
       recordIds: [documentId]  
     });*/  
   },  
 })  

JS Helper:
 ({  
       UpdateDocument : function(component,event,Id) {  
     var action = component.get("c.UpdateFiles");  
     var fName = component.find("fileName").get("v.value");  
     //alert('File Name'+fName);  
     action.setParams({"documentId":Id,  
              "title": fName,  
              "recordId": component.get("v.recordId")  
              });  
     action.setCallback(this,function(response){  
       var state = response.getState();  
       if(state=='SUCCESS'){  
         var result = response.getReturnValue();  
         console.log('Result Returned: ' +result);  
         component.find("fileName").set("v.value", " ");  
         component.set("v.files",result);  
       }  
     });  
     $A.enqueueAction(action);  
   },  
 })  

Apex Controller:
 public class SimplyfyFilesCntrl {  
   @AuraEnabled  
   public static List<ContentDocument> getFiles(string recordId){  
     List<ContentDocument> DocumentList = new List<ContentDocument>();  
     Set<Id> documentIds = new Set<Id>();  //store file ids
     List<ContentDocumentLink> cdl=[select id,LinkedEntityId,ContentDocumentId from ContentDocumentLink where LinkedEntityId=:recordId];  
     for(ContentDocumentLink cdLink:cdl){  
       documentIds.add(cdLink.ContentDocumentId);  // Document ids
     }      
     DocumentList = [select Id,Title,FileType,ContentSize,Description from ContentDocument where id IN: documentIds];  
     return DocumentList;  
   }  
   @AuraEnabled  
   public static List<ContentDocument> UpdateFiles(string documentId,string title,string recordId){  
     system.debug('title: ' +title);  
     ContentDocument cd = [select id,title from ContentDocument where Id=:documentId]; // Getting files from Parent record 
     cd.Title = title;  // Changing file Title with user entered title
     try{  
       update cd;  // Update ContentDocument (File)
     }  
     catch(DMLException e){  
       system.debug('Exception has occurred! ' +e.getMessage());  
     }  
      List<ContentDocument> DocumentList = new List<ContentDocument>();  
     Set<Id> documentIds = new Set<Id>();  
     List<ContentDocumentLink> cdl=[select id,LinkedEntityId,ContentDocumentId from ContentDocumentLink where LinkedEntityId=:recordId];  
     for(ContentDocumentLink cdLink:cdl){  
       documentIds.add(cdLink.ContentDocumentId);  
     }      
     DocumentList = [select Id,Title,FileType,ContentSize,Description from ContentDocument where id IN: documentIds];  
     return DocumentList;  // Return list of files on parent record
   }  
 }  



Output:
Steps to create quick Action:

  • Go to object manager and click on the Object for that you want to create 'New Action'.
  • Click on 'Buttons Links and Action' and then click on 'New Action' button.
  • Select the 'Action Type' Lightning Component.
  • Select Lightning Component that you just created.
  • Enter Label and Name and click on Save button.
  • Go to object page layout and add this action there.



























































Hope this post helps many.

Thanks
Arun Kumar.

Share:

Friday, 21 September 2018

Get geolocation coordinates in the Visualforce Page

Scenario: Sometimes we need to track current location on SFDC record field update, in that case, we have to get the current coordinates where the record field is updated/inserted.

In this blog, I am going to implement the above functionality using custom visualforce page with standard controller and extension. Javascript is going to be used to get the current coordinates based on the events fired by the user (field insert/update).

The main pain point is to get the coordinates on Text Area (Rich) field on change (change event) because 'onchange' will not work with Text Area (Rich) field, so I am using CKEditor here to track the changes in the Text Area (Rich) field.

Visualforce page:
 <apex:page standardController="Account" extensions="GeoLocationCntrlExtn" sidebar="false">  
  <html>  
    <head>  
      <apex:slds />  
      <style>  
        .slds-scope .slds-table_bordered tbody td, .slds-scope .slds-table_bordered tbody th, .slds-scope .slds-table--bordered tbody td, .slds-scope .slds-table--bordered tbody th{  
           border-top: 0px;  
         }  
          .slds-scope .slds-page-header{  
         border-radius: 0px;  
         box-shadow: 0 0px 0px 0 rgba(0, 0, 0, 0.1);  
         }  
         .bodyPart{  
           border: 1px solid #E5E5E5;  
         }  
      </style>  
    </head>  
    <body>  
      <div class="slds bodyPart">  
        <div class="slds-grid">  
          <div class="slds-col slds-size--12-of-12">  
            <div class="slds-page-header">  
              <span style=""> New Account </span>  
            </div>  
          </div>  
        </div>  
        <div class="slds-grid">  
          <div class="slds-col slds--size-12-of-12">  
            <apex:form >  
              <!-- ******************* Action Function ********************************************-->  
               <apex:actionFunction action="{!calLocation}" name="calculateLocation" reRender="">  
                 <apex:param value="" name="longitude" assignTo="{!longitude}" />  
                 <apex:param value="" name="latitude" assignTo="{!latitude}" />  
              </apex:actionFunction>  
              <apex:outputpanel id="test">  
              </apex:outputpanel>  
              <!-- **********************************************************************************-->  
              <span style="color:red;" id="locationError"> </span>  
              <table class="slds-table slds-table--bordered slds-no-row-hover slds-max-medium-table_stacked">  
                <tbody>  
                  <tr>  
                    <td>Name</td>  
                    <td><apex:inputfield value="{!Account.Name}" styleClass="slds-input"/></td>  
                  </tr>  
                  <tr>  
                    <td>Site Address</td>  
                    <td><apex:inputfield value="{!Account.Address__c}" styleClass="slds-input"/></td>  
                  </tr>  
                </tbody>  
              </table><br/>  
              <center>  
                <apex:commandButton value="Save" action="{!SaveRecord}" styleClass="slds-button slds-button--brand" />  
                <apex:commandButton value="Cancel" action="{!CancelAction}" styleClass="slds-button slds-button--neutral" immediate="true"/>  
            </center><br/>  
            </apex:form>  
          </div>  
        </div>  
      </div>  
      <!--*********** JS script **************>  
      <script>  
       for (var i in CKEDITOR.instances) {  
         CKEDITOR.instances[i].on('change', function() {  
           var x=document.getElementById('locationError');  
             if (!navigator.geolocation){  
             return;  
             }  
              function success(position) {  
               var latitude = position.coords.latitude;  
               var longitude = position.coords.longitude;  
               //alert('latitude: '+ latitude );  
               //alert('longitude: '+ longitude );  
               calculateLocation(longitude,latitude);  
             }  
             function error() {  
             //alert(error.code);  
             switch(error.code) {  
                 case error.PERMISSION_DENIED:  
                   x.innerHTML = "User denied the request for Geolocation."  
                   break;  
                 case error.POSITION_UNAVAILABLE:  
                   x.innerHTML = "Location information is unavailable."  
                   break;  
                 case error.TIMEOUT:  
                   x.innerHTML = "The request to get user location timed out."  
                   break;  
                 case error.UNKNOWN_ERROR:  
                   x.innerHTML = "An unknown error occurred."  
                   break;  
                 }  
             }  
             navigator.geolocation.getCurrentPosition(success, error);  
         });  
        }  
   </script>  
    </body>  
  </html>  
 </apex:page>  

********************* Apex Controller **************************************

 public with sharing class GeoLocationCntrlExtn {  
   public String longitude{get;set;}  
   public String latitude{get;set;}  
   private Account acc;  
   public GeoLocationCntrlExtn(ApexPages.StandardController controller) {  
     this.acc = (Account)controller.getRecord();  
   }  
   public pageReference calLocation(){  
     return null;  
   }  
   public pageReference SaveRecord(){  
      if((acc.Address__c!=null && acc.Address__c!='') )  
        acc.Address__c += ' latitude: '+latitude+' longitude: '+longitude;  
      try{  
        insert acc;  
          PageReference pageRef = new PageReference('/'+ acc.Id );  
          return pageRef ;  
        }  
        catch(DMLException dmle){  
          ApexPages.addmessage(new ApexPages.message(ApexPages.Severity.ERROR,'Error: ' + dmle.getdmlMessage(0) ));  
         system.debug('Exception has occured! '+dmle.getMessage() );  
        return null;  
        }  
   }  
    public pageReference CancelAction(){  
     return null;  
   }  
 }  






Share:

Integrate Salesforce using MailChimp webhook

Real-time record updates (Contact/Lead) based on MailChimp Unsubscribe, Subscribe, email change event.


MailChimp is a marketing automation platform, which provides a good REST API (MailChimp API 3.0) to communicate with other systems. MailChimp provides 'MailChimp for Salesforce' Appexchange app to integrate Salesforce to Mailchimp. Through this app, we can sync the Contact, Lead, Campaign from Salesforce to MailChimp and vise versa.

In this blog, I am going to demonstrate how can we listen to the change made on the MailChimp side and update the record in the Salesforce based on the change event.

MailChimp webhook allow us to collect the changed information based on the events (like Unsubscribe, Subscribe, Email Change, etc). To get the changed data we have to provide the URL for webhook, that URL going to be our Salesforce public site URL.

Below are the steps we need to follow to implement the functionality.

  • Create a Visulforce page that will listen to the changes from MailChimp.

  •  <apex:page controller="MCUnsubscribeCntrl" sidebar="false" showHeader="false" action="{! UpdateUnsubscribeMembers}" >  
       MailChimp Webhooks...
     </apex:page>  
    

  • Create an Apex controller for the Visualforce page, this Apex class will get the changed data from MailChimp and update the record in the Salesforce.
    
    public without sharing class MCWebhookCntrl {
        
        public PageReference UpdateUnsubscribeMembers() {
            String sCode = fetchSecrateCode();
            System.debug('***Secrate Code: ### ' + sCode );
            String MCrequestCode = ApexPages.currentPage().getParameters().get('code');
            if (MCrequestCode == null || !sCode.equals(MCrequestCode )) {
                System.debug('Code is not valid: Please use valid code: ' + MCrequestCode );
                return null;
            }
            String eventType= ApexPages.currentPage().getParameters().get('type');
            system.debug('Type of event: '+ eventType);
            
            if(eventType == 'upemail'){ //Email Changed event
                // Getting New Email
                String newEmail = ApexPages.currentPage().getParameters().get('data[new_email]');
                system.debug('Changed Email: ' +newEmail);
                // Getting Old Email
                String oldEmail = ApexPages.currentPage().getParameters().get('data[old_email]');
                system.debug('Old Email: ' +oldEmail );
                if (oldEmail == null || oldEmail.length() <= 0) {
                    System.debug('Email Id is null or blank');
                    return null;
                }
                else {
                    System.debug('Email for Email Opt Out: ' + oldEmail );
                }
                String safeEmail = String.escapeSingleQuotes(oldEmail);
                safeEmail.replace('*', '\\*');
                safeEmail.replace('?', '\\?');
                // SOSL search for records (Contact)
                List<List<SObject>> searchList = [FIND :oldEmail IN EMAIL FIELDS RETURNING Contact(id) limit 1]; 
                //Contact List  
                List<Contact> contacts = ((List<Contact>)searchList[0]);
                // Getting first record. you can iterate the list to get all records.
                List<Contact> conListForUpdate = [select id,Email,MailChimpActivity__c from Contact where id =: contacts[0].id ];
                for (Contact c : conListForUpdate ) {
                    if( c.MailChimpActivity__c ==null)
                         c.MailChimpActivity__c ='';
                    c.MailChimpActivity__c += 'Email chnaged from '+c.Email+ ' to '+newEmail+'  \r\n' ;
                    c.Email = newEmail;
                }
                // Update Lists
                if (conListForUpdate.size() > 0) {
                    update conListForUpdate;
                }
            }
            // Unsubscribe/ Subscribe
            else if(eventType != 'profile'){
                
                String emailAddr = ApexPages.currentPage().getParameters().get('data[email]');
                if (emailAddr == null || emailAddr .length() <= 0) {
                    System.debug('Email Id is null or blank');
                    return null;
                }
                else {
                    System.debug('Email for Email Opt Out: ' + emailAddr );
                }
                String safeEmail = String.escapeSingleQuotes(emailAddr);
                safeEmail.replace('*', '\\*');
                safeEmail.replace('?', '\\?');
                // SOSL search for records (Contact/Lead)
                List<List<SObject>> searchList = [FIND :emailAddr IN EMAIL FIELDS RETURNING Contact(id), Lead(id) ]; 
                //Contact List  
                List<Contact> contacts = ((List<Contact>)searchList[0]);
                //Lead List
                List<Lead> leads = ((List<Lead>)searchList[1]);
                for (Contact c : contacts) {
                    if(eventType =='subscribe'){
                        c.HasOptedOutOfEmail = false;
                    }else if(eventType =='unsubscribe'){
                        c.HasOptedOutOfEmail = true;
                    }
                   
                }
                for (Lead l : leads) {
                    if(eventType =='subscribe'){
                        l.HasOptedOutOfEmail = false;
                    }else if(eventType =='unsubscribe'){
                        l.HasOptedOutOfEmail = true;
                    }
                    
                }
                // Update Lists
                if (contacts.size() > 0) {
                    update contacts;
                }
                if (leads.size() > 0) {
                    update leads;
                }
            }
            return null;
        } 
        
        public String fetchSecrateCode() {
            try {
                StaticResource sr = [ SELECT Body, Name FROM StaticResource WHERE Name = 'WebHooksConfig' LIMIT 1 ];
                String xml = sr.Body.toString();
                System.debug('XML data: ' + xml);
                //XML parsing
                Dom.Document doc = new Dom.Document();
                doc.load(xml);
                Dom.XMLNode webhooks = doc.getRootElement();
                for (Dom.XMLNode node : webhooks.getChildElements()) {
                    String id = node.getAttribute('id', '');
                    if (id.equalsIgnoreCase('unsubscribe')) {
                        return node.getAttribute('code', '');
                    }
                }
            }
            catch (Exception e) {
                System.debug('*** Something went wrong: Error Occured! : ' + e.getMessage());
            }
            return null;
        }
    }
    

  • Create an XML file. Upload this file in the static resource, static resource Name will be 'WebHooksConfig'.
  •  <webhooks>  
     <webhook id="unsubscribe" code="YOUR_SECRETE_CODE"/>  
     </webhooks> 
  • Create the public site (force.com site) in Salesforce.
  • Go to quick search and type - Site -> click Sites -> Create a domain if it is not created. Otherwise, click on the 'New' button to create a new site. 
  • Click on 'Public Access Settings' and give the view/edit access Contact/Lead for fields. e.g- (HasOptedOutOfEmail, MailChimpActivity__c )
    Also, give the access for Apex class and Visualforce page.






  • Create Webhook for MailChimp List. Login into your MailChimp account and click on 'List' tab.






    • Enter Webhook URL:( https://YourOrgDomain-developer-              edition.ap5.force.com/webhooks/MCUnsubscribe?code=YOUR_SECRETE_CODE). Select the types of updates to be sent (Subscribe, Unsubscribe, email changed) and click on Save.


    Result:

    Now, If you made an update on the MailChimp side like if you Unsubscribe the member from MailChimp then that record Contact/Lead (with the same email) inside salesforce will be updated with 'Email Opt Out' checkbox checked. If the user again Subscribe that member then 'Email Opt Out' field will be Unchecked.
    If the Email of member changed from MailChimp side then the Field on Contact (MailChimpActivity__c) will be updated.

    Hope this blog post helps many.

    Thanks.
    Share:
    Trailhead Profile


    Follow by Email

    Total Pageviews

    Followers

    Popular Posts

    Powered by Blogger.

    Contact form

    Name

    Email *

    Message *