0

I am building a custom file upload component as follows. I prepare my list of uploaded files inside the function handleFilesUpload. I then call another function called handleSaveFiles but inside of this function the variable that holds the list of files is empty. Can someone point me to why that is please?

docs.html

<template>
  <div class="slds-m-around_medkium">
    <lightning-input type="file" 
    onchange={handleFilesUpload}
    multiple>
    </lightning-input>
  </div>
 <template if:true={showUpload}>
  <section role="dialog" tabindex="-1" aria-modal="true" aria-labelledby="modal-heading-01">
      <div>
          <button onclick={closePopUp}>x</button>
          <div>
              <h1>Select File Type</h1>
          </div>
          <div>
              <div>
                  <lightning-combobox name="File Type" label="File Type" value="" placeholder="Choose File Type" options={filetype}
                      onchange={handleChange}></lightning-combobox>
              </div>
          </div> 
      </div>
  </section>
  <div role="presentation"></div>
</template>
</template>

docs.js

import { LightningElement, api, track, wire } from 'lwc';
import getFiles from '@salesforce/apex/docsUpload.getFiles';
import saveFiles from '@salesforce/apex/docsUpload.saveFiles';

export default class docs extends LightningElement {

    @api recordId
    filesUploaded = [];
    showUpload = false;
    filetype = { id: 1, label: "Item 1" },
        { id: 2, label: "Item 2" },
        { id: 3, label: "Item 3" }
    ]
       
    connectedCallback() {
        this.loadFiles();
    }

    loadFiles() {
        getFiles({appId: this.recordId})
        .then(result => {
            console.log(JSON.stringify(result));
        })
        .catch(error => {
            console.log(JSON.stringify(error));
        });
    }

    handleFilesUpload(event) {
        let files = event.target.files;
        let filescount = files.length;
        let readOpearationCount = 0;
        if(files.length > 0) {
            let filesName = '';
            for(let i = 0; i < files.length; i++) {
                let file = files[i];
                filesName = filesName + file.name + ',';
                let freader = new FileReader();
                freader.onload = () => {
                    var fileContents = freader.result.split(',')[1]
                    this.filesUploaded.push({'fileName':file.name, 'fileContent':fileContents});
                };
                freader.onloadend = () => {
                    readOpearationCount++;
                    if(readOpearationCount == filescount) {
                        this.showUpload = true;
                        this.handleSaveFiles();
                    }
                };
                freader.readAsDataURL(file);
            }
        }
    }

    handleSaveFiles() {
        console.log(JSON.stringify(this.filesUploaded));
    
        saveFiles({
            recordId: this.recordId,
            fileData: JSON.stringify(this.filesUploaded)
        })
        .then(result => {
            if(result){
                this.filesUploaded = [];
                console.log('file uploaded successfully.');
            }
            else {
                console.log('error during file upload');
            }
        }).catch(error => {
            console.log('error');
        });
    }
}

1 Answer 1

0

freader.onload runs only when the read operation triggered by readAsDataURL (or readAsText) ends and only if the file has been read successfully. Since you call this.handleSaveFiles() right after the for-loop, it will always run before the first onload handler. That's why filesUploaded is empty.

You should call this.handleSaveFiles() only after every file has been read. To keep track of occured read, you could leverage loadend event since it's fired when a file read has completed, successfully or not, while load event is fired just when a file has been read successfully.

handleFilesUpload(event) {
    const files = event.target.files;
    const filesCount = files.length;
    let readOperationCount = 0;
    for(const file of files) {
        const freader = new FileReader();
        freader.onload = () => {
            const fileContents = freader.result.split(',')[1]
            this.filesUploaded.push({'fileName': file.name, 'fileContent': fileContents});
        };
        freader.onloadend = () => {
            readOperationCount++;
            if (readOperationCount === filesCount) {
                // Call handleSaveFiles only when every file has been read
                this.handleSaveFiles();
            }
        };
        freader.readAsDataURL(file);
    }
}

Please note that readAsDataURL reads file's data as a base64 encoded string, if you're reading text files (like csv or json) you could use readAsText avoiding base64 encode/decode operations.

5
  • @RubenDB, thank you. That fixed my issue. If I may ask another question. I have a custom picklist field on the ContentVersion table called Type. The values for Type__c are: "Proof of Income", "Proof of Residency", etc. When a file gets uploaded via the custom LWC I am building as above, how can I present a user with a list of values from the field Type so that they can tag the file as one of the types?
    – degmo
    Commented Sep 7, 2023 at 14:27
  • @degmo You're welcome, glad I could help. You could leverage either a lightning-input-field or a combobox and getPicklistValues wire adapter. Provide a default value and attach an handler for the change event, storing the selected value in an instance variable, then add it as input parameter to saveFiles method.
    – RubenDG
    Commented Sep 7, 2023 at 18:52
  • Hi @RubenDG thank you again. I need a little more help if you could. I put a prototype based on your suggestion to allow for selection of a file type. I updated the HTML file to icnlude a "dialog" section that pops up when "showupload" is true. However, save operation goes on while the dialog is still open. how can I make SaveFiles wait until the dialog is closed? Much appreciated. This is my first LWC component so I do appreciate your help..my experience so far has been with backend development
    – degmo
    Commented Sep 8, 2023 at 15:58
  • @degmo If the user should select first the type, then upload the file, I would modify the html having first <template lwc:if={showUpload} (please note that if:true and if:false are deprecated, you should use lwc:if|elseif|else directive), then, after closing that template element, I would put the container of lightning-input: <div class="slds-m-around_medium" lwc:else>. In the modal I would add a button to confirm the selection of the file type. This button would toggle showUpload. Another way to achieve this is disable the lightning-input until the user choose a file type.
    – RubenDG
    Commented Sep 8, 2023 at 16:46
  • anyway comments are not meant for extended discussion. Moreover you should not change the original question. Therefore I think opening a new question would be the best. This way you could get advice from the whole community.
    – RubenDG
    Commented Sep 8, 2023 at 16:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .