How to use the File Upload Lightning Web Component

File Uploader
Files Uploading

Insert this component into a Salesforce record page and implement it in a Salesforce Lightning Community for guest users.

Introduction

Salesforce provides a component blueprint that makes it easy to implement. It is available for use for both authenticated users and guest users on a Salesforce Lightning Community.

lightning-file-upload

The lightning-file-upload component creates file records in Salesforce without developers needing to write Apex for authenticated users. It provides attributes to pass values such as a record id to associate a file to an object record, provided the user has permission to access the object. Another attribute is “onuploadfinished”, where a developer can write logic to process once a file upload operation is complete. The component supports multiple file uploads. As soon as a user opens a file to upload, this component immediately inserts it into Salesforce. There is no option to suppress the default behavior. In some use cases, we want to have the upload process execute after an event, such as clicking on a submit button. In that scenario, a custom lightning-input element is necessary.For authenticated users, pass a record id to the attribute, record-id.Using lightning-file-upload for guest users in a Community is a bit more nuanced. First, configure settings to allow guest users to upload files. Then create a sharing rule to grant users access to the object to associate the file. To associate a record id to each file, and create an object record first, and fetch its id to create the relationship.

Salesforce File Settings

Salesforce File Settings

How to pass a record id to the lightning-file-upload for guest users:

  1. To enable guest users to upload files to a record, the org admin can create a custom field on the ContentVersion object. The field type can be text or picklist. The API name of the custom field must end with fileupload__c. For example, you can use the API name Guest_Record_fileupload__c for the custom field.
  2. Specify the file-field-name and file-field-value attributes in lightning-file-upload to store a value in the custom field in the ContentVersion object. For example, set file-field-name to Guest_Record_fileupload__c. Set file-field-value to a value to use in Apex to associate the file to the record.
    • At this point, create a record first to fetch the record id. For example, you can implement some trickery to coerce a user to click a “Next” button. Then a handler would execute to create a Case record at that point. Then the lightning-file-upload component would conditionally render and appear on the page to prompt the user to upload a file.
    • Specify the file-field-name and file-field-value attributes in lightning-file-upload to store a value in the custom field in the ContentVersion object. For example, set file-field-name to Guest_Record_fileupload__c. Set file-field-value to a value to use in Apex to associate the file to the record, such as the case record id on a custom Contact Us form.
  3. Write a before insert Apex trigger on the ContentVersion object to read the record id from the custom field and write it to the ContentDocumentId field. This will create the link between the ConventVersion object and its related case record.

The lightning-file-upload component works best for authenticated users because limited logic is necessary to associate the file to its related record. If a developer wants to control when a file uploads, consider writing a custom lightning-input element.

<template>
    <lightning-file-upload
            label="Attach file"
            name="fileUploader"
            accept={acceptedFormats}
            record-id={recordId}
            onuploadfinished={handleUploadFinished} // event handler
            multiple>
    </lightning-file-upload>
</template>
import { LightningElement, api } from 'lwc';
export default class FileUploadExample extends LightningElement {
    @api
    recordId;

    get acceptedFormats() {
        return ['.pdf', '.png', '.jpg', '.tiff'];
    }

    handleUploadFinished(event) {
        // Get the list of uploaded files
        const uploadedFiles = event.detail.files;
        console.log("No. of files uploaded : " + uploadedFiles.length);
    }
}

lightning-input type=”upload”

Collecting user input such as a form often includes using a lightning-input element. The “upload” type provides the same look and user experience as lightning-file-upload. This requires more involved JavaScript and Apex processing. We still need to create a record first and fetch its record id to pass it with each file that to insert into Salesforce. This custom solution allows control of a file read and inserted without the need of an Apex trigger. This element is not specific to Salesforce and is a standard JavaScript component. We will be using JavaScript Promises to set up our files reads finish first so that our Apex code does not execute prematurely.How to pass record id to lightning-input type=”upload” for guest users:

  1. Write an event handler to capture event.target.files.
    • Contain these files in an array, to await processing when the “Submit” button is clicked.
  2. Build files when the button is clicked. Iterate over each file, and instantiate a new FileReader object with a JavaScript Promise
    • JavaScript promise allows us to ensure the order in our operation. Because the file read takes time, execution of code proceeds and our Apex would execute immediately. We need to tell Apex to wait.
    • Read each file, use onload method which executes after a file read. We then resolve each promise in the iteration. This means that the promise settles or “locks-in” to match the state of another promise. The resolve returns a JavaScript object that we then append to an array.
    • We populate an array of JavaScript promises that contains details of each file to pass along with the record id to our Apex.
    • Execute Promise.all(): This method executes all promises that we built and collected in our array simultaneously, and returns the results of all promises in one value, an array of JavaScript objects. We then pass this array of JavaScript objects to our Apex along with the record id to create files in Salesforce and associate them to the related record. We contain our Apex call in a chained promise. We told the Apex to hold off on executing until we call the promise.then() method which represents a chained promise, to associate further action with a promise that becomes settled. We pass the return values from our settled promises into our Apex. Apex takes over and creates ContentVersion records and with the record id link.
Template HTML
<template>
    <div>
        <lightning-input accept={acceptedFormats} 
                        label={field.label} 
                        required={field.isRequired} 
                        onchange={handleAddFiles} 
                        type="file">
        </lightning-input>
    </div>
    <br/>
    <template if:true={fileUploaded}>
        <lightning-button-icon class="slds-p-right_x-small" icon-name="action:close" onclick={removeFiles} alternative-text="Remove document" size="x-small"></lightning-button-icon>
        <template for:each={fileNamesArr} for:item="fileName">
            <span key={fileName.Name} class="slds-text-body_medium slds-text-color_success slds-p-right_x-small">
                {fileName.Name}
            </span>
        </template>
    </template>
</template>
JavaScript
import { LightningElement, api } from 'lwc';
export default class FileUploadExample extends LightningElement {
    @api
    recordId;

    @track fileUploaded = false; // Render template
    @track fileNamesArr = []; // Display file names
    filesArr = []; // Store read file objects to pass to Apex
    filePromises = []; // Called my Promise.all
    @track showSpinner = true;

    get acceptedFormats() {
        return ['.pdf', '.png', '.jpg', '.tiff'];
    }

    // Handles front end; build FileReader object in a helper and call apex
    handleAddFiles(event) {
        if (event.target.files.length > 0) {

            // Validate file size
            for(let i = 0; i < event.target.files.length; i++) {
                let file = event.target.files[i]
                if(file.size > 1000000 ) {
                    // console.log('File too large, size: ' + file.size)
                    this.throwError(this.label.fileSizeError + ' ' + file.name); // Custom labels used
                    return;
                }
            }

            this.fileUploaded = true; // Show element
            if (this.isIdentificationRequired) { // Condition for multiple files
                for (let i = 0; i < event.target.files.length; i++) {
                    let file = event.target.files[i];
                    this.fileNamesArr.push({Name: file.name}); // Iterate html
                    this.filesArr.push(file);
                }
            } else { // Single file upload
                let file = event.target.files[0];
                this.fileNamesArray = [];
                this.fileNamesArr.push({Name: file.name});
                this.filesArr.push(file);
            }
        }
    }
    
    // Called by Submit button
    buildFile() { 
        for (let i = 0; i < this.filesArr.length; i++) {
            let build = new Promise((resolve, reject) => {
                    let freader = new FileReader();
                    freader.readAsDataURL(this.filesArr[i]); // reads file contents
                    freader.onload = f => {    // executes after successful read
                        let base64 = 'base64,';
                        let content = freader.result.indexOf(base64) + base64.length;
                        let fileContents = freader.result.substring(content);

                        resolve({ // returns a value after successful promise
                            Title: this.filesArr[i].name, // Store file name
                            VersionData: fileContents
                        })

                    };
                })
            this.filePromises.push(build); // filePromises called by Promise.all()
        }
        return Promise.all(this.filePromises) // Execute all file builds asynchronously
        .then(result => {
            this.handleSaveFiles(this.recordId, result) // Pass file objects to Apex
        }) 
    }

    removeFiles(event)
    {
        this.fileUploaded = false;
        this.showSpinner = false;
        this.files = undefined; 
        this.fileNamesArr = []; 
        this.filesArr = [];
        this.filesUploaded = [];
    }

    handleSaveFiles(recordId, result) 
    {
        console.log('handleSaveFiles recordId: ' + recordId);
        saveFiles({ recordId: recordId, filesToInsert: result})
        .then(data => {            
            const showSuccess = new ShowToastEvent({
                title: this.label.success, // Custom label use
                message: this.fileNamesArr.length + ' ' + this.label.fileUploadSuccess,
                variant: 'Success',
            });
            this.dispatchEvent(showSuccess); 
            this.fileNamesArray = [];
        })
        .catch(error => {
            const showError = new ShowToastEvent({
                title: 'Error!!',
                message: 'An Error occur while uploading the file. ' + error.message,
                variant: 'error',
            });
            this.dispatchEvent(showError);
        });
    }
}
Apex Method
// Apex Method
@AuraEnabled
    public static list<Id> saveFiles(Id recordId, list<Object> filesToInsert){
        
        list<Id> lstCntVerIds = new list<Id>();
        List<ContentVersion> lstVersionsToInsert = new List<ContentVersion>();
        
        try {
            
            if (recordId == null) {
                system.debug('Record id is null');
                throw new AuraHandledException('No record id for ContentVersion record insertion');
            }
            
            for (Object file : filesToInsert) {
                FileInfo fileData = (FileInfo)JSON.deserialize(JSON.serialize(file), FileInfo.class);
                ContentVersion objCntVersion = new ContentVersion();
                objCntVersion.Title = fileData.Title;
                objCntVersion.PathOnClient = '/' + fileData.Title;
                objCntVersion.FirstPublishLocationId = recordId;
                objCntVersion.VersionData = fileData.VersionData;
                objCntVersion.IsMajorVersion = true;
                lstVersionsToInsert.add(objCntVersion);
            }
            
            insert lstVersionsToInsert;
            
        } catch(Exception e) {
            
            system.debug('ContentVersion insert error: ' + e.getMessage());
            throw new AuraHandledException(e.getMessage());
        }
        
        return lstCntVerIds;
    }

    public class FileInfo {
        public String Title;
        public Blob VersionData;
    }
Apex Test Method
// Apex test method
@IsTest
    private static void createContentVersionSuccess() {
        
        Case newCase = new Case(
                Description = 'What\'s your favorite color? Blue',
                Status = 'New'
        );

        insert newCase;

        Blob b=EncodingUtil.base64Decode('Unit Test Attachment Body'); 
        List<LwcUtils.FileInfo> fInfoList = new List<LwcUtils.FileInfo>();
        LwcUtils.FileInfo singlefileInfo = new LwcUtils.FileInfo();
        singlefileInfo.Title = 'ABC';
        singlefileInfo.VersionData = b;
        fInfoList.add(singlefileInfo);      

        Test.startTest();
        Case createdCase = LwcUtils.createCaseWithContact(newCase, newContact);
        LwcUtils.saveFiles(newCase.id, fInfoList);
        Test.stopTest();
        
        List<ContentVersion> cv = [SELECT Id, FirstPublishLocationId FROM ContentVersion];
        List<ContentDocumentLink> cd = [SELECT LinkedEntityId FROM ContentDocumentLink WHERE LinkedEntityId =: newCase.Id];
        
        System.assertNotEquals(null, cv);
        System.assertEquals(newCase.Id, cv[0].FirstPublishLocationId);
        System.assertEquals(newCase.Id, cd[0].LinkedEntityId);
    }

References

posted February 2, 2021

Read more: 

Troubleshoot Einstein Bots: Model Build
Salesforce CMS: What it is, what it isn’t

Check the articles below

February 11, 2021

How to resolve a model build error.IntroductionAs you

February 8, 2021

Salesforce CMSSalesforce CMS is a hybrid content management

February 4, 2021

Qualify and resolve routine customer requests automatically.CT DOLIntroductionEinstein

>