Today, we are going to take a deep dive into File Upload component.
If you are building Lightning Component, you can simply use this code below to include a File Upload component:
<lightning:fileUpload name="fileUploader"
label= "File Upload"
multiple="{!v.multiple}"
accept="{!v.accept}"
disabled="{!v.disabled}"
recordId="{!v.recordId}"
onuploadfinished="{! c.handleUploadFinished }"/>
If you are using Flow Builder, it is even easier for you to simply add the File Upload component into your flow like this:
Make sure you have set the related record ID so that the uploaded files will be able to show in related files.
But... is it really satisfying our basic needs?
Don't you think the component is missing something very important, for example, you might want to view the files that are uploaded, or delete the uploaded files?
I was hoping that Salesforce would make improvement on that, but I don't know whether this request will be even considered or not, so I figured why don't I implement a custom file upload component then?
Aren't you interested to know how to create your own custom advanced file upload component like this where you can actually see the uploaded files and delete them as needed?
Without further ado, let's get into the code together!
First, let's create a controller for this Lightning Component. Let's call it LightningFileUploadHandler.apxc
:
// LightningFileUploadHandler.apxc
public class LightningFileUploadHandler {
@AuraEnabled
public static List<ContentDocument> getFiles(String recordId){
Set<Id> recordIds = new Set<Id>{recordId};
List<ContentDocumentLink> cdlList = [SELECT Id, LinkedEntityId, ContentDocumentId FROM
ContentDocumentLink WHERE LinkedEntityId IN :recordIds
];
Set<Id> documentIds = new Set<Id>();
for(ContentDocumentLink cdl:cdlList){
documentIds.add(cdl.ContentDocumentId);
}
return [SELECT Id, Title, FileType FROM ContentDocument WHERE Id IN :documentIds];
}
@AuraEnabled
public static void deleteFile(String contentDocumentId){
delete [SELECT Id from ContentDocument WHERE Id = :contentDocumentId];
}
}
getFiles()
is used to retrieve the uploaded files associated with the current record and deleteFile()
is used to delete the ContentDocument
file.
Next, we need to create our main Lightning Component LightningFileUploadComponent.cmp
:
<!-- LightningFileUploadComponent.cmp -->
<aura:component controller="LightningFileUploadHandler" implements="lightning:availableForFlowScreens,force:appHostable,force:hasRecordId" 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="acceptFileTypes" type="String" />
<aura:attribute name="showFileHeader" type="Boolean" default="true" />
<aura:attribute name="fileHeaderText" type="String" default="Files" />
<aura:attribute name="uploadMultiple" type="Boolean" default="true"/>
<aura:attribute name="showSpinner" type="boolean" default="false"/>
<div class="slds-card slds-card_boundary">
<!-- show or hide header -->
<aura:if isTrue="{!v.showFileHeader}">
<div class="slds-page-header">{!v.fileHeaderText}</div>
</aura:if>
<div class="slds-align_absolute-center">
<lightning:fileUpload multiple="{!v.uploadMultiple}"
accept="{!v.acceptFileTypes}"
recordId="{!v.recordId}"
onuploadfinished="{!c.uploadFinished}" />
</div>
<br/>
<div class="slds-form--compound">
<table class="slds-table slds-table--bordered">
<!-- hide the table header if file is empty -->
<aura:if isTrue="{!not(empty(v.files))}">
<thead>
<tr>
<th>Title</th>
<th>File Type</th>
<th></th>
</tr>
</thead>
</aura:if>
<tbody>
<aura:iteration items="{!v.files}" var="f">
<tr>
<td>
<a href="javascript:void(0)" id="{!f.Id}" onclick="{!c.previewFile}">
{!f.Title}
</a>
</td>
<td>{!f.FileType}</td>
<td>
<a href="javascript:void(0)" id="{!f.Id}" onclick="{!c.deleteSelectedFile}">
<lightning:icon size="x-small" iconName="utility:delete" alternativeText="Delete" />
</a>
</td>
</tr>
</aura:iteration>
</tbody>
</table>
<!-- show spinner logo when needed -->
<aura:if isTrue="{!v.showSpinner}">
<div class="slds-spinner_container">
<div class="slds-spinner slds-spinner--medium" aria-hidden="false" role="alert">
<div class="slds-spinner__dot-a"></div>
<div class="slds-spinner__dot-b"></div>
</div>
</div>
</aura:if>
</div>
</div>
</aura:component>
Since we are going to use the component in our flow, the component needs to implement lightning:availableForFlowScreens
. I didn't include other interfaces such as flexipage:availableForAllPageTypes
or flexipage:availableForRecordHome
into this component because I have included the specific attribute types in the design resource which are not supported by those interfaces. If you do so, you will see an error message like this:
files
attribute from the LightningFileUploadComponent.design
, not a big deal. You can always retrieve the files again using Get Records
action in Flow Builder.
Next, we are going to look at LightningFileUploadComponentController.js
:
// LightningFileUploadComponentController.js
({
doInit : function(component, event, helper){
helper.getUploadedFiles(component, event);
},
previewFile : function(component, event, helper){
$A.get('e.lightning:openFiles').fire({
recordIds: [event.currentTarget.id]
});
},
uploadFinished : function(component, event, helper) {
helper.getUploadedFiles(component, event);
var toastEvent = $A.get("e.force:showToast");
// show toast on file uploaded successfully
toastEvent.setParams({
"message": "Files have been uploaded successfully!",
"type": "success",
"duration" : 2000
});
toastEvent.fire();
},
deleteSelectedFile : function(component, event, helper){
if( confirm("Confirm deleting this file?")){
component.set("v.showSpinner", true);
helper.deleteUploadedFile(component, event);
}
}
})
We define the functions as below:
doInit()
: called automatically when component is initiatedpreviewFile()
: click to display the file screen in SalesforceuploadFinished()
: callback function when the files are successfully uploaded, display a toast afterwardsdeleteSelectedFile()
: show a confirmation dialog box to confirm file deletion
And our LightningFileUploadComponentHelper.js
:
// LightningFileUploadComponentHelper.js
({
getUploadedFiles : function(component, event){
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();
component.set("v.files",result);
}
});
$A.enqueueAction(action);
},
deleteUploadedFile : function(component, event) {
var action = component.get("c.deleteFile");
action.setParams({
"contentDocumentId": event.currentTarget.id
});
action.setCallback(this,function(response){
var state = response.getState();
if(state=='SUCCESS'){
this.getUploadedFiles(component);
component.set("v.showSpinner", false);
// show toast on file deleted successfully
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
"message": "File has been deleted successfully!",
"type": "success",
"duration" : 2000
});
toastEvent.fire();
}
});
$A.enqueueAction(action);
},
})
Every time getUploadedFiles()
is called, we retrieve the files from Apex
to make sure the data is up-to-date. deleteUploadedFile()
will be called when the user clicks on the delete icon, then display a toast on file successfully deleted.
A little css
styling to make the file upload button larger in LightningFileUploadComponent.css
:
// LightningFileUploadComponent.css
.THIS .slds-file-selector__body{
padding: 1em;
}
Lastly, we need to define the LightningFileUploadComponent.design
so that we can pass in input variables to the component and receive outputs in the next flow screen.
<!-- LightningFileUploadComponent.design -->
<design:component>
<design:attribute name="recordId" label="Record Id" />
<design:attribute name="showFileHeader" label="Show File Header?" />
<design:attribute name="fileHeaderText" label="File Header Text" />
<design:attribute name="uploadMultiple" label="Upload Multiple?" />
<design:attribute name="acceptFileTypes" label="Accept File Types (.pdf, .jpg and etc.)" />
<design:attribute name="files" label="Files" />
</design:component>
You can decide what needs to pass into the component.
That's all! If you have followed along, you will be able to see something like this:
-
Select files to upload.
-
Click
Done
to continue.
-
View the uploaded files instantly. You can click the on title link to view the file or the delete icon to delete the file.
New Updates
For those who are seeking for test class to get the code coverage, here's one for your reference:
// LightningFileUploadHandlerTest.apxc
@IsTest
public class LightningFileUploadHandlerTest {
@IsTest
public static void LightningFileUploadHandlerTestMethod() {
Account acc = new Account(
BillingStreet = '123 ST',
BillingState = 'NY',
BillingCountry = 'US',
BillingPostalCode = '00000',
BillingCity = 'City',
Name = 'Test'
);
insert acc;
ContentVersion cv = new ContentVersion(
Title = 'Test',
PathOnClient = 'test.jpg',
VersionData = Blob.valueOf('Test Content'),
IsMajorVersion = true
);
insert cv;
ContentDocument cd = [SELECT Id, Title, LatestPublishedVersionId FROM ContentDocument LIMIT 1];
//create ContentDocumentLink record
ContentDocumentLink cdl = New ContentDocumentLink(
LinkedEntityId = acc.id,
ContentDocumentId = cd.Id,
ShareType = 'V'
);
insert cdl;
LightningFileUploadHandler.getFiles(acc.Id);
LightningFileUploadHandler.deleteFiles(cd.Id);
}
}
Hope you guys enjoy reading this post! The code is referenced and inspired by the post from this website.
Post was published on , last updated on .
Like the content? Support the author by paypal.me!