function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Major1507Major1507 

lwc upload a file

I have a custom lwc component to upload multiple files in a given record. I want that the files uploaded under the Notes and Attachment section of that record to be displayed in a datatable on the same custom component and the user should have access to remove the attachment from the component rather than going to the record and deleting it.
Below is my code:

Markup:
<template>
    <div class="ds_custom_body">
        <div class="ds_width-container js-enabled">
            <div class={formGroupClass}>
                <label class="ds_label" for="file-upload">
                    
                <img id="u21_img" class="img " src="https://d1icd6shlvmxi6.cloudfront.net/gsc/7CR7K2/66/9d/a7/669da75ef5c94006bba8c6b217a48a59/images/return_on_investment__3__1/file_upload_u21.svg?pageId=63f05657-437f-4a8d-8fa3-42798c6e5946">
                &nbsp;&nbsp;&nbsp;&nbsp;
                    {fileUploadLabel}
                </label>
                <span id="file-upload-error" class="ds_error-message slds-hyphenate" if:true={hasErrors}>
                    <span class="ds_visually-hidden">Error:</span>&nbsp;{errorMessage}
                </span>
                <input class={inputClass} id="file-upload" name="file-upload" type="file" accept={acceptedFormats}
                    multiple onchange={handleFilesChange} aria-describedby="file-upload-error" hidden />

                <div if:true={fileNames}>
                    {fileNames}
                </div><br />
                <template if:true={data}>
                    <lightning-card title="Uploaded Files" icon-name="standard:account">
                        <div style="width: auto;">
                            <lightning-datatable data={data} 
                                                 columns={columns} 
                                                 key-field="id">
                            </lightning-datatable>
                        </div>
                    </lightning-card>
                </template>
            </div>
        </div>
    </div>
</template>

Js Controller:
import { LightningElement, wire, api, track } from 'lwc';
 import { FlowAttributeChangeEvent } from 'lightning/flowSupport';
 import saveFiles from '@salesforce/apex/FileUploadController.saveFiles';
 import { MessageContext, publish, subscribe, unsubscribe } from 'lightning/messageService';
 import REGISTER_MC from '@salesforce/messageChannel/registrationMessage__c';
 import VALIDATION_MC from '@salesforce/messageChannel/validateMessage__c';
 import VALIDATION_STATE_MC from '@salesforce/messageChannel/validationStateMessage__c';

 const columns = [{
    label: 'Title',
    fieldName: 'FileName',
    type: 'url',
    typeAttributes: {
        label: {
            fieldName: 'Title'
        },
        target: '_blank'
    }
}];
 
 export default class GovFileUpload extends LightningElement {
     
     @api fieldId = "uploadField";
     @api fileUploadLabel = "Upload a file";
     @api acceptedFormats = "image/png, image/jpg, .pdf, .doc, .docx, .zip";
     @api maxFileSizeInMB = 2;
     @api required = false;
     @api errorMessage = "Select a file"; 
 
     @api filesUploadedCollection = []; 
     @api filesUploadedExpanded = []; 
     @api filesUploaded; 
 
     @api useApexToSaveFile;   
     @api recordId = "";
     
     @track hasErrors = false;
     @track fileNames = '';
     @track columns = columns;
 
     get formGroupClass() {
         let formGroupClass = "";
         formGroupClass =  this.hasErrors ? "ds_form-group ds_form-group--error" : "ds_form-group";
         return formGroupClass;
     }
 
     get inputClass() {
         let inputClass = "";
         inputClass =  this.hasErrors ? "ds_file-upload ds_file-upload--error" : "ds_file-upload";
         return inputClass;
     }
 
     handleFilesChange(event) {
         this.clearError();
         let files = event.target.files;
         if(files.length > 0) {
             let filesName = '';
             for (let i = 0; i < files.length; i++) {
                 let file = files[i];
                 filesName = filesName + file.name + ',';
                 if(file.size > (this.maxFileSizeInMB * 1000000)) {
                     this.hasErrors = true;
                     this.errorMessage = `The selected file(s) must be smaller than ${this.maxFileSizeInMB} MB`;
                     return;
                 }
                 let freader = new FileReader();
                 freader.onload = f => {
                     let base64 = 'base64,';
                     let content = freader.result.indexOf(base64) + base64.length;
                     let fileContents = freader.result.substring(content);
                     if (i==0) {
                         this.filesUploaded = file.name;
                     } else {
                         this.filesUploaded = this.filesUploaded + ';' + file.name;
                     }
                     this.filesUploadedCollection.push(file.name);
                     this.filesUploadedExpanded.push({
                         Title: file.name,
                         VersionData: fileContents
                     });
                     this.useApexToSaveFile = true;
                     if(this.recordId !== "" && this.useApexToSaveFile && (i+1) === files.length) {
                         this.handleSaveFiles();
                     } 
                     if((i+1) === files.length) {
                         this.dispatchUploadEvent();
                     }  
                 };
                 freader.readAsDataURL(file);
             }
             this.fileNames = filesName.slice(0, -1);
         }
     }
 
     handleSaveFiles() {
         saveFiles({
             filesToInsert: this.filesUploadedExpanded,
             strRecId: this.recordId
          })
         .then(data => {
         })
         .catch(error => {
             this.hasErrors = true;
             this.errorMessage = error;
         }); 
     }
 
     // messaging attributes
     @wire(MessageContext) messageContext;
     validateSubscription;
 
     // LMS functions
     subscribeMCs() {
         if (this.validateSubscription) {
             return;
         }
         this.validateSubscription = subscribe (
             this.messageContext,
             VALIDATION_MC, (message) => {
                 this.handleValidateMessage(message);
             });
     }
 
     unsubscribeMCs() {
         unsubscribe(this.validateSubscription);
         this.validateSubscription = null;
     }
 
     connectedCallback() {
         // subscribe to the message channels
         this.subscribeMCs();
 
         // publish the registration message after 0.1 sec to give other components time to initialise
         setTimeout(() => {
             publish(this.messageContext, REGISTER_MC, { componentId: this.fieldId });
         }, 100);
     }
 
     disconnectedCallback() {
         this.unsubscribeMCs();
     }
 
     handleValidateMessage(message) {
         this.handleValidate();
     }
 
     @api 
     handleValidate() {
         this.hasErrors = false;
         if(this.required && this.filesUploadedExpanded.length === 0) {
             this.hasErrors = true;
         }
         publish(this.messageContext, VALIDATION_STATE_MC, {
             componentId: this.fieldId,
             isValid: !this.hasErrors,
             error: this.errorMessage
         });
         return !this.hasErrors;
     }
 
     @api 
     clearError() {
         this.hasErrors = false;
     }
 
     dispatchUploadEvent() {
         // tell the flow engine about the upload
         const attributeChangeEvent = new FlowAttributeChangeEvent('value', JSON.stringify(this.filesUploaded));
         this.dispatchEvent(attributeChangeEvent);
 
         // tell any parent components about the upload
         const fileUploadEvent = new CustomEvent('fileUpload', {
             detail: {
                 id: this.fieldId,
                 value: JSON.stringify(this.filesUploaded)
             }
         });
         this.dispatchEvent(fileUploadEvent);
     }

     removeReceiptImage(event) {
        var index = event.currentTarget.dataset.id;
        this.filesData.splice(index, 1);
    }
 
 }

MetaXml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <description>This help users select and upload a file.</description>
    <masterLabel>Gov File Upload</masterLabel>
    <targets>
        <target>lightning__FlowScreen</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="fieldId" type="String" label="Field Id" required="true" default="uploadField" description="Field Id - must be unique to this process"/>
            <property name="fileUploadLabel" type="String" label="Field Label" default="Upload a file" description="The label to display"/>
            <property name="acceptedFormats" type="String" label="Accepted File Formats" default="image/png, image/jpg, .pdf, .doc, .docx, .zip" description="A valid case-insensitive filename extension, starting with a period (.) character. For example: .jpg, .pdf, or .doc."/>
            <property name="maxFileSizeInMB" type="Integer" label="Max Files Size Allowed (in MB)" default="2" description="The maximum file size allowed to upload in MB (Max Value should not be greater than Salesforce Standards)"/>
            <property name="required" type="Boolean" label="Required?" default="false" description="If True, user must enter at least one file."/>
            <property name="errorMessage" type="String" label="Error Message" default="Select a file" description="Error Message to display"/>
            <property name="useApexToSaveFile" type="Boolean" label="Use Apex Function to Save File"  default="true" description="Must be set to True - {!$GlobalConstant.True}. [This feature is not yet enabled]"/>
            <property name="recordId" type="String" label="Related Record ID" description="The ID of the Salesforce record to associate the files with.  A value must be entered or the flow will fail."/>        
            <property name="filesUploaded" type="String" label="Uploaded file names" description="Semi-colon delimited list of file names uploaded. Text variable. Output only."/>
            <property name="filesUploadedCollection" type="String[]" label="Uploaded file names Collection" description="A collection variable to hold the names of any files uploaded. Output only."/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Apex Controller:
public with sharing class FileUploadController {
    
    @AuraEnabled
    public static void saveFiles(List<Object> filesToInsert, String strRecId) { 
        List<Id> lstCntVerIds = new List<Id>();
        List<ContentVersion> lstVersionsToInsert = new List<ContentVersion>();
        for (Object objFile : filesToInsert) {
            FileInfo fileData = (FileInfo)JSON.deserialize(JSON.serialize(objFile), FileInfo.class);
            ContentVersion objCntVersion = new ContentVersion();
            objCntVersion.PathOnClient = fileData.Title;
            objCntVersion.Title = fileData.Title;
            objCntVersion.VersionData = fileData.VersionData;
            lstVersionsToInsert.add(objCntVersion);
        }
        
        if(!lstVersionsToInsert.isEmpty()) {
            List<Database.SaveResult> res = Database.INSERT(lstVersionsToInsert);
            for (Database.SaveResult saveResult : res) {
                if(saveResult.isSuccess()) {
                    lstCntVerIds.add(saveResult.getId());
                }
            }
            List<ContentDocumentLink> lstCntDocLinkToInsert = getLstCntDocLink(lstCntVerIds, strRecId);
            if(!lstCntDocLinkToInsert.isEmpty()) {
                try {
                    List<Database.SaveResult> res2 = Database.INSERT(lstCntDocLinkToInsert);
                } catch (DmlException dmlEx) {
                    throw new AuraHandledException('Could not insert document to database. Check user permissions ' + dmlEx.getMessage());
                }
            }
        }  
    }
    
    private static List<ContentDocumentLink> getLstCntDocLink(List<Id> lstCntVerIds, String strRecordId) {
        List<ContentDocumentLink> lstCntDocLink = new List<ContentDocumentLink>();
        List<ContentVersion> lstContVersion = new List<ContentVersion>();
        if(!lstCntVerIds.isEmpty()) {
            if(ContentVersion.SObjectType.getDescribe().isAccessible()) {
                lstContVersion = [SELECT ContentDocumentId FROM ContentVersion WHERE Id IN :lstCntVerIds];
            }
            for(ContentVersion objCntVer : lstContVersion) {
                ContentDocumentLink objCntDocLink = new ContentDocumentLink();
                objCntDocLink.ContentDocumentId = objCntVer.ContentDocumentId;
                objCntDocLink.LinkedEntityId = strRecordId;
                objCntDocLink.ShareType = 'V';
                lstCntDocLink.add(objCntDocLink);
            }
        }
        return lstCntDocLink;
    }
    
    public class FileInfo {
        public String Title;
        public Blob VersionData;
    }
    
}

​​​​​​​​​​​​​​
Vinay MVinay M

Hi Major1507,

  I see that you are using "ContentDocumentLink" and "ContentVersion" in Apex to upload files. This will not make them appear under "Notes and Attachments" section, rather they will go under "Files" related list. Files is the newer version for Attachments in Salesforce. You will have to work with "Attachment" object in Apex if you need them to display under Notes and Attachment section. Coming to delete, if its a file, the default behaviour is that if the user is not the owner (uploaded person in this case), they will not be able to delete it. For this you may need to write Apex which will perform delete opetaion and implements "without sharing". If not, they will need to have "Modify All Permissions" (which is not ideal to grant to every user). 

Please mark this as best answer if it solves your problem.

Thank you,

Vinay.