Filepond in a Lightning Web Component for Salesforce upload – Part 3
This short series of posts will be a how-to on setting up an enhanced upload component for Salesforce. This could be used both on a Salesforce record page or indeed in a community. Part 1 got the scratch org setup, the library loaded as a resource and Part 2 setup a Lightning Web Component and got the component working – up to the point of uploading an actual document.
Here is our current code for the main fileUploadComponent.js file:
import { LightningElement } from 'lwc'; import filepond from "@salesforce/resourceUrl/Filepond"; import { loadStyle, loadScript } from "lightning/platformResourceLoader"; import { ShowToastEvent } from "lightning/platformShowToastEvent"; export default class FileUploadComponent extends LightningElement { filepondInitialized = false; pond; renderedCallback() { if (this.filepondInitialized) { return; } this.filepondInitialized = true; Promise.all([ loadScript(this, filepond + "/filepond.min.js"), loadScript(this, filepond + "/filepond-plugin-image-preview.min.js"), loadScript(this, filepond + "/filepond-plugin-file-validate-size.min.js"), loadStyle(this, filepond + "/filepond.min.css"), loadStyle(this, filepond + "/filepond-plugin-image-preview.min.css") ]) .then(() => { this.initialiseFilePond(); }) .catch((error) => { this.dispatchEvent( new ShowToastEvent({ title: "Error loading filepond", message: error.message, variant: "error" }) ); }); } initialiseFilePond() { const divElement = this.template.querySelector("div.createFilePond"); FilePond.registerPlugin( FilePondPluginImagePreview, FilePondPluginFileValidateSize ); const pond = FilePond.create({ name: "myfilepond", allowRemove: false, allowRevert: false, allowImagePreview: true, imageResizeTargetWidth: 100, imageResizeTargetHeight: 100, maxFiles: 10, maxFileSize: "3MB", allowMultiple: true }); divElement.appendChild(pond.element); } }
Lets get a progress bar working for the upload. The document is a bit daunting at https://pqina.nl/filepond/docs/api/server/ but likelwsie it is trying to be flexible in approach and not aware of individual circumstances, but it turns out easy to do and follow – although not for those that come from APEX into JavaScript.
As part of the set-up of the component we can use the server property to access what the documentation call end-points that will be used to do the upload. I will use a function called uploadHelper which can be entered outside of the create command to make the file easier to read and understand. So we will add:
server: { process: ( fieldName, file, metadata, load, error, progress, abort, transfer, options ) => { this.uploadHelper(file, progress, load); } }
to to create component to make:
const pond = FilePond.create({ name: "myfilepond", allowRemove: false, allowRevert: false, allowImagePreview: true, imageResizeTargetWidth: 100, imageResizeTargetHeight: 100, maxFiles: 10, maxFileSize: "3MB", allowMultiple: true, server: { process: ( fieldName, file, metadata, load, error, progress, abort, transfer, options ) => { this.uploadHelper(file, progress, load); } } });
Lets setup the uploadHelper method which will call the saveToFile method so lets start that blank:
uploadHelper(file, progress, load) { const fileReader = new FileReader(); fileReader.onloadend = () => { this.saveToFile(fileReader.result, file.name, load); }; fileReader.onprogress = (e) => { progress(e.lengthComputable, e.loaded, e.total); }; fileReader.readAsDataURL(file); } saveToFile(result, name, load) { }
So the uploadHelper is called by the FilePond component and then we can use a standard Javascript object called a FileReader to read the file from the user via the browser. See https://developer.mozilla.org/en-US/docs/Web/API/FileReader for more information. This has a method called readAsDataURL which starts the reading of the file and we can listen to the events of loadend and progress via the onloadend and onprogress. The onprogress we then use to let our FilePond component updates its progress state by passing in how far through the upload we are. See https://pqina.nl/filepond/docs/api/server/#process-1 for an example.
So this does two things, provides an upload process but also allows us to get a call to the saveToFile method when we have the data to upload. We are passing the result of the upload (this will be the actual raw data of the file) , the filename and then the load end-point from the FilePond component.
All we need to do now is to upload the data into Salesforce with an apex class. This will be decoded as Base64 so we have to remove the first part of the data returned – see documentation at https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
So to get the fileContents we will upload we need this code at the start of our saveToFile method:
saveToFile(result, name, load) { let base64 = "base64,"; let startOfContent = result.indexOf(base64) + base64.length; const fileContents = result.substring(startOfContent); }
We are going to need an apex class to do the upload into the object. So create a new fileUploadController class and add the following code:
public with sharing class fileUploadController { @AuraEnabled public static string saveFile( Id idParent, String strFileName, String base64Data ) { // Decoding base64Data base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8'); // inserting file ContentVersion cv = new ContentVersion(); cv.Title = strFileName; cv.PathOnClient = '/' + strFileName; cv.FirstPublishLocationId = idParent; cv.VersionData = EncodingUtil.base64Decode(base64Data); cv.IsMajorVersion = true; insert cv; return cv.Id; } }
This will receive in the Id of the object to attach the record to, the name of the file and then the data. Going back to our fileUploadComponent.js we are going to need to import the saveFile methed, add the api decorator of the lwc import and finally add the recordId to the class:
import { LightningElement,api } from 'lwc'; ... ... import saveFile from "@salesforce/apex/fileUploadController.saveFile";
and
@api recordId
This will automatically be populated when we add it to a recordPage in Salesforce. Add to our saveToFile method:
saveToFile(result, name, load) { let base64 = "base64,"; let startOfContent = result.indexOf(base64) + base64.length; const fileContents = result.substring(startOfContent); saveFile({ idParent: this.recordId, strFileName: name, base64Data: encodeURIComponent(fileContents) }) .then((result) => { load(result); // Showing Success message after file insert this.dispatchEvent( new ShowToastEvent({ title: "Success", message: "Uploaded Successfully", variant: "success" }) ); }) .catch((error) => { // Showing errors if any while inserting the files console.log(JSON.stringify(error)); this.ErrorMessage = error; this.dispatchEvent( new ShowToastEvent({ title: "Error while uploading File", message: error.message, variant: "error" }) ); }); }