Upload a file to Salesforce File from Lightning Component

In my recent work for a client, there was a need to upload a file to a case not as an attachment but upload under Files Section. As you are aware, Salesforce Files are replacing Attachment. In this post, I am sharing the code end to end for your reference.

In this case, I created a new case and attached file under it, you can also pass a recordId/parentId if you already have the record to which files needs to be attached

FileUpload Component

<aura:component description="FileUpload" implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" controller="FileUploadController">
 
    <aura:attribute name="recordId" type="String" description="Record to which the files should be attached" />
    <aura:attribute name="fileName" type="String" default="No File Selected.." />
    
    
        <lightning:layoutItem class="slds-p-around--xx-small" size="12" smallDeviceSize="12" mediumDeviceSize="12" largeDeviceSize="12">

        <div class="slds-grid slds-wrap">
            <div class="slds-size_2-of-2">

              <lightning:card>
                    <aura:set attribute="actions">
            			<button class="slds-button slds-button_neutral">Upload Offline File</button>
                    </aura:set>
                  
                    <div class="slds-p-bottom_medium slds-p-left_medium slds-border_bottom">
                        <div class="slds-media">
                            <div class="slds-media__figure slds-p-right_medium">
                            <div class="slds-icon slds-page-header__icon ">
                                <lightning:icon iconName="action:user" size="small"/>
                            </div>
                            </div>
                            <div class="slds-media__body">
                                <div class="slds-p-top_small">
                                <h1>
                                    <span class="slds-page-header__title" title="Upload File">Upload File</span>
                                </h1>
                                </div>
                            </div>
                        </div>
                        </div>


                        <div class="slds-p-around_medium">

                            <div style="overflow: auto;" >
                                <div style="min-width: 600px;">
                                    <div class="slds-p-around--small">
                                       
                                        <!-- Lightning Input with file type and on file change call the 'handleFilesChange' controller -->
                                        <lightning:input aura:id="fuploader" onchange="{!c.handleFilesChange}" type="file" name="file" label="Upload File" multiple="false"/>
                                        <div class="slds-text-body_small slds-text-color_error">{!v.fileName} </div>
                                        <br/>
                                        <lightning:button label="Save" onclick="{!c.handleSave}"
                                                        variant="brand" class="slds-m-top--medium"/>

                                        
                                    </div>
                                </div>
                            </div>
                       
                        </div>

                </lightning:card>
 
            </div>
        </div>
    
        </lightning:layoutItem>
    </div>

    
    
 </aura:component>

Controller Js File

({
	handleUploadFinished : function(component, event, helper) {
        var uploadedFiles = event.getParam("files");
        var documentId = uploadedFiles[0].documentId;
        var fileName = uploadedFiles[0].name;
        helper.showToast('Success','Success','File Uploaded successfully');        
    },
    
    handleSave: function(component, event, helper) {

        if (component.find("fuploader").get("v.files")!=null && component.find("fuploader").get("v.files").length > 0) {
            helper.uploadHelper(component, event);
        } else {
            helper.showToast('Error','Error','Please Select a Valid File');
        }
    },
     
    handleFilesChange: function(component, event, helper) {
        var fileName = 'No File Selected..';
        if (event.getSource().get("v.files").length > 0) {
            fileName = event.getSource().get("v.files")[0]['name'];
        }
        component.set("v.fileName", fileName);
    },


})

Helper Js File

({
    
    MAX_FILE_SIZE: 4500000, //Max file size 4.5 MB 
    CHUNK_SIZE: 750000,      //Chunk Max size 750Kb 
     
    uploadHelper: function(component, event) {
        var fileInput = component.find("fuploader").get("v.files");
        var file = fileInput[0];
        var self = this;
        if (file.size > self.MAX_FILE_SIZE) {
            component.set("v.fileName", 'Alert : File size cannot exceed ' + self.MAX_FILE_SIZE + ' bytes.\n' + ' Selected file size: ' + file.size);
            return;
        }
         
        var objFileReader = new FileReader();
        objFileReader.onload = $A.getCallback(function() {
            var fileContents = objFileReader.result;
            var base64 = 'base64,';
            var dataStart = fileContents.indexOf(base64) + base64.length;
             
            fileContents = fileContents.substring(dataStart);
            self.uploadProcess(component, file, fileContents);
        });
         
        objFileReader.readAsDataURL(file);
    },
     
    uploadProcess: function(component, file, fileContents) {
        var startPosition = 0;
        var endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
        this.uploadInChunk(component, file, fileContents, startPosition, endPosition, '');
    },
     
     
    uploadInChunk: function(component, file, fileContents, startPosition, endPosition, attachId) {
        var getchunk = fileContents.substring(startPosition, endPosition);
        var action = component.get("c.uploadFile");
        action.setParams({
            fileName: file.name,
            base64Data: encodeURIComponent(getchunk),
            contentType: file.type,
            fileId: attachId
        });
         
        // set call back 
        action.setCallback(this, function(response) {
            attachId = response.getReturnValue();
            var state = response.getState();
            if (state === "SUCCESS") {
                // update the start position with end postion
                startPosition = endPosition;
                endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
                if (startPosition < endPosition) {
                    this.uploadInChunk(component, file, fileContents, startPosition, endPosition, attachId);
                } else {
                    this.showToast('Success','Success','File Uploaded successfully');   
                }
            } else if (state === "INCOMPLETE") {
                this.showToast('Error','Error','Issue in uploading File '+response.getReturnValue());   
            } else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + errors[0].message);
                        this.showToast('Error','Error','Issue in uploading File '+errors[0].message);   
                    }
                } else {
                    console.log("Unknown error");
                }
            }
        });
        // enqueue the action
        $A.enqueueAction(action);
    },

    showToast : function(type, title, message) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "type": type,
            "title": title,
            "message": message
        });
        toastEvent.fire();
    },
         
    
})

FileUploadController.cls

public with sharing class FileUploadController {

    private static final String ISSUE_UPLOADING_FILE = 'There is a problem in uploading the file';
    
    @AuraEnabled
    public static Id uploadFile(String fileName, String base64Data, String contentType) {
        
        Contact instructorContact = UserContactRetriever.getContact();
        Case caseObj = new Case(Status='Working',Origin='Community',Subject='Offline Attendance Upload',Contact=instructorContact);
        try{  
            insert caseObj;        
            base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');              
            ContentVersion cv = createContentVersion(base64Data, filename);
            ContentDocumentLink cdl = createContentDocumentLink(cv.Id, caseObj.Id);
            if (cv == null || cdl == null) { return null; }
            return cdl.Id;   
            }         
         catch(Exception exc) {
            System.debug('Error: ' + exc.getStackTraceString() + ' ' + exc.getMessage());
            Logger.logMessage(exc.getMessage(),'User: '+UserInfo.getName(),'INST_FileUploadController','SaveFile()',String.valueOf(exc.getLineNumber()),exc.getTypeName(), exc.getStackTraceString() ,null);  
            throw new AuraHandledException(ISSUE_UPLOADING_FILE);
        }   
    }
    
    private static ContentVersion createContentVersion(String base64, String filename) {
        ContentVersion cv = new ContentVersion();
        cv.VersionData = EncodingUtil.base64Decode(base64);
        cv.Title = filename;
        cv.ContentLocation = 'S';
        cv.PathOnClient = filename;
          insert cv;
          return cv;
      }
  
      private static ContentDocumentLink createContentDocumentLink(String contentVersionId, String recordId) {
           if (contentVersionId == null || recordId == null) { return null; }
              
            ContentDocumentLink cdl = new ContentDocumentLink();
            cdl.ContentDocumentId = [
              SELECT ContentDocumentId 
              FROM ContentVersion 
              WHERE Id =: contentVersionId
            ].ContentDocumentId;
    
            cdl.LinkedEntityId = recordId;
            cdl.ShareType = 'V';
            cdl.Visibility = 'InternalUsers';
              insert cdl;
              return cdl;
          }    
    
}

Permanent link to this article: https://salesforcebuddy.com/2020/06/upload-a-file-to-salesforce-file-from-lightning-component/

Lightning:recordForm

A lightning:recordForm component lets you to create forms to add, view, or update a record. Using this component to create record forms is easier than building forms manually with lightning:recordEditForm and lightning:recordViewForm.

Additionally, this component takes care of FLS(field-level security) and sharing internally

The component accepts a mode value that determines the user interaction allowed for the form.

edit – Creates an editable form to add a record or update an existing one. When updating an existing record, specify the recordId.
view – Creates a form to display a record that the user can also edit. The record fields each have an edit button. View mode is the default, and as such, can be omitted.
readonly – Creates a form to display a record without enabling edits. The form doesn’t display any buttons.

Creating a Record

Use mode=”edit” and pass in the object API name for the record to be created. Specify the fields using the fields attribute, or layoutType attribute to load all the fields defined on the given layout.Because no recordId is passed, edit mode loads the form with input fields that aren’t populated with field data. The form displays Submit and Cancel buttons

Editing a Record

Use mode=”edit” and pass the ID of the record and the corresponding object API name to be edited. Specify the fields using the fields attribute, or layoutType attribute to load all the fields defined on the given layout.When a recordId is passed, edit mode loads the form with input fields displaying the specified record’s field values. The form also displays Submit and Cancel buttons.

Viewing a Record Read-Only

Use mode=”readonly” and pass the ID of the record and the corresponding object API name to be displayed. Specify the fields using the fields attribute, or layoutType attribute to display all the fields defined on the given layout.The readonly mode loads the form with output fields only, and without Submit or Cancel buttons

Lightning Component Code

<aura:component implements="flexipage:availableForAllPageTypes,force:appHostable,flexipage:availableForRecordHome,force:hasRecordId,force:hasSObjectName" access="global" >
    <aura:attribute name="sObjectName" type="String" default="Contact" />
    <aura:attribute name="recordId" type="String" default="00336000004H6YMAA0" />    
    
    <lightning:card iconName="standard:contact" title="Create Mode : lightning:recordForm">
        
        <div class="slds-p-left_large slds-p-right_medium">	
            <lightning:recordForm aura:id="recordViewForm" 
                                  objectApiName="{!v.sObjectName}"
                                  fields="Name,Email,Phone"
                                  columns="2"
                                  layoutType ="Full"
                                  mode="edit"
                                  onsuccess="{!c.onSuccess}"
                                  onsubmit="{!c.onSubmit}"
                                  onload="{!c.onLoad}"
                                  onerror="{!c.onError}"
                                  />   
            
        </div>
    </lightning:card>
    <lightning:card iconName="standard:contact" title="Edit Mode : lightning:recordForm">
        <div class="slds-p-left_large slds-p-right_medium">	
            <lightning:recordForm aura:id="recordViewForm" 
                                  objectApiName="{!v.sObjectName}"
                                  columns="2"
                                  fields="Name,Email,Phone"
                                  recordId="{!v.recordId}"
                                  layoutType ="Full"
                                  mode="View"
                                  onsuccess="{!c.onSuccess}"
                                  onsubmit="{!c.onSubmit}"
                                  onload="{!c.onLoad}"
                                  onerror="{!c.onError}"
                                  />   
            
        </div>
    </lightning:card>
    
    <lightning:card iconName="standard:contact" title="Read Only Mode : lightning:recordForm">
        <div class="slds-p-left_large slds-p-right_medium">	
            <lightning:recordForm aura:id="recordViewForm" 
                                  objectApiName="{!v.sObjectName}"
                                  columns="2"
                                  fields="Name,Email,Phone"
                                  recordId="{!v.recordId}"
                                  layoutType ="Compact"
                                  mode="readonly"
                                  onsuccess="{!c.onSuccess}"
                                  onsubmit="{!c.onSubmit}"
                                  onload="{!c.onLoad}"
                                  onerror="{!c.onError}"
                                  />   
            
        </div>
    </lightning:card>
    
</aura:component>

Js Code

({
    onSuccess : function(component, event, helper) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "title": "Success!",
            "message": "The record has been Saved successfully."
        });
        toastEvent.fire();
    },
    onSubmit : function(component, event, helper) {
    },
    onLoad : function(component, event, helper) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "title": "Loaded!",
            "message": "The record has been Loaded successfully ."
        });
        toastEvent.fire();
    },
    onError : function(component, event, helper) {
        var toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams({
            "title": "Error!",
            "message": "Error."
        });
        toastEvent.fire();
    }
    
    
})

Permanent link to this article: https://salesforcebuddy.com/2020/05/lightningrecordform/

The entity name must immediately follow the ‘&’ in the entity reference

Did you ever encounter an error like this while working on Aura components ?

 <lightning:tab label=”Skills & Competencies” id=”skils”>          

   Content here!!

  </lightning:tab>

Salesforce is complaining about the special character &, in order to fix it , change it to

<lightning:tab label=”Skills &amp; Competencies” id=”skils”>

and it works like a charm.

Permanent link to this article: https://salesforcebuddy.com/2020/05/the-entity-name-must-immediately-follow-the-in-the-entity-reference/