A blog about Salesforce CRM Configuration and development

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 *