Thursday, 10 March 2022

How to retrieve all the files by traversing folder and sub folders on drag and drop using LWC


OUTLINE
While working on one of the requirements for a manufacturing sector solutions customer based out in Atlanta, Georgia, there was a requirement to traverse\navigate folder and sub folders to get all the files dropped by the user.

CHALLENGE Our main challenge was reading each entry in the directory (folder) or sub-directory (subfolder) dropped by the user to retrieve all the files within the directory (folder).

SOLUTION
  • To solve this issue, we've used JavaScript async function where each dataTransfer.item is converted to webkitGetAsEntry and checked whether it is a directory or a file.
  • If it is a fileEntry then it will be stored in array variable but if it is a directory then an async function named as traverseDirectory will be called and Directory entry will passed as a parameter.
  • In traverseDirectory function a reader variable is created and returned a promise, an anonymous function is associated with promise in which we created an array(local) variable and defined a function named as readEntries.
  • readEntries will traverse the directory.
    • if directory is empty then resolve will be return with all the promises.
    • if directory is not empty then further it will check for fileEntry.
      • If it is a fileEntry then it will be pushed into the array (local) variable.
      • If it is not a fileEntry then traverseDirectory will be called recursively and sub directory will be passed as a parameter.
  • And if, any error occurs then reject will be returned with an error.
Sample code is as below:

FileUploader.html

<template> 
    <lightning-card title="File Uploader"> 
        <div class="slds-align_absolute-center slds-p-bottom_medium"> 
            <form id="fileUploadForm"> 
                <div class="slds-form-element slds-align_absolute-center"> 
                    <span class="slds-form-element__label" id="file-selector-primary-label"></span> 
                    <div class="slds-form-element__control"> 
                        <div class="slds-file-selector slds-file-selector_files"> 
                            <div class="slds-file-selector__dropzone slds-has-drag-over slds-grid slds-wrap" 
                                ondrop={dropHandler} ondragover={dragOverHandler}> 
                                <div class="slds-m-around_xx-large"> 
                                    Drop File(s)/Folder 
                                </div> 
                            </div> 
                        </div> 
                    </div> 
                </div> 
            </form> 
        </div> 
        <template if:true={showFilePropertiesModal}> 
            <section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" 
                aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open"> 
                <div class="slds-modal__container"> 
                    <header class="slds-modal__header"> 
                        <button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" 
                            title="Close" onclick={handleCloseModal}> 
                            <lightning-icon icon-name="utility:close" alternative-text="close" variant="inverse" 
                                size="small" onclick={handleCloseModal}></lightning-icon> 
                            <span class="slds-assistive-text">Close</span> 
                        </button> 
                        <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Set File Properties 
                        </h2> 
                    </header> 
                    <div class="slds-modal__content slds-var-p-around_large" id="modal-content-id-1"> 
                        <div class="slds-card slds-var-p-around_large"> 
                                <template for:each={files} for:item="file"> 
                                    <div class="slds-grid slds-gutters" key={file.name} > 
                                        <div class='slds-col'> 
                                            <lightning-input label="File Name" type="text" value={file.name}> 
                                            </lightning-input> 
                                        </div> 
                                        <div class='slds-col'> 
                                            <lightning-combobox name="types" label="Document Type" 
                                                placeholder="Select File Type" options={options} 
                                                onchange={handleFileTypeSelectionChange} required> 
                                            </lightning-combobox> 
                                        </div> 
                                    </div> 
                                </template> 
                        </div> 
                    </div> 
                    <footer class="slds-modal__footer">
                        <button class="slds-button slds-button_neutral" onclick={handleCloseModal} 
                            title="Cancel">Cancel</button> 
                        <button class="slds-button slds-button_brand" onclick={handleFileUpload} 
                            title="Upload">Upload</button> 
                    </footer> 
                </div> 
            </section> 
            <div class="slds-backdrop slds-backdrop_open"></div> 
        </template> 
    </lightning-card> 
</template>


FileUploader.Js

import { LightningElement, api, track} from 'lwc'; 

export default class FileUploader extends LightningElement { 
    @api recordId; 
    @track showFilePropertiesModal=false; 
    @track showDocumentPropertiesForm=false; 
    @track files=[]; 
    @api options=[{label:'Print Card', value:'Print_Card'},{label:'Vertices Output', value:'Vertices_Output'}]; 

 
    dragOverHandler(event){ 
        event.preventDefault(); 
    } 

    dropHandler(event){ 
        event.stopPropagation(); 
        event.preventDefault(); 
        this.handleDroppedContent(event.dataTransfer.items); 

    } 

    async handleDroppedContent(items) { 
        var isDirectory = false; 
        var isFile = false; 

        for (let i = 0; i < items.length; i++) { 
            let item = items[i].webkitGetAsEntry(); 
            if (item.isDirectory) { 
                if (isFile) { 
                    this.files = []; 
                    console.log('File and Folder combination are not allowed to upload'); 
                    return; 
                } else if (isDirectory) { 
                    this.files = []; 
                    console.log('Multiple Folders are not allowed to upload'); 
                    return; 
                } 
                else { 
                    isDirectory = true; 
                } 
            } else if (item.isFile) { 
                if (isDirectory) { 
                    this.files = []; 
                    console.log('File and Folder combination are not allowed to upload'); 
                    return; 
                } else { 
                    isFile = true; 
                } 
            } 
        } 
        for (let i = 0; i < items.length; i++) { 
            let item = items[i].webkitGetAsEntry(); 
            if (item) { 
                if (item.isDirectory) { 
                    await this.traverseDirectory(item).then((result) => {                                                 
                        this.pushResultIntoFiles(result); 
                    }); 
                } else if (item.isFile) { 
                    this.files.push(item); 
                } 
            } 
        } 
        this.convertFileEntryToFile();         
        this.handleCachedFile(); 
    } 

    async traverseDirectory(entry) { 

        let _this = this; 
        const reader = entry.createReader(); 
        return new Promise((resolve, reject) => { 
            const iterationAttempts = []; 
            function readEntries() { 

                reader.readEntries((entries) => { 

                    if (!entries.length) { 
                        resolve(Promise.all(iterationAttempts)); 
                    } else { 
                        iterationAttempts.push(Promise.all(entries.map((ientry) => { 

                            if (ientry.isFile) { 
                                return ientry; 
                            } 
                            return _this.traverseDirectory(ientry); 
                        }))); 
                        readEntries(); 
                    } 
                }, error => reject(error)); 
            } 
            readEntries(); 
        }); 
    } 

    handleCachedFile(){ 
        if(this.files.length>0){ 
           this.showFilePropertiesModal=true; 
            this.showDocumentPropertiesForm=true; 
        } 
        else{ 
            console.log('Error'); 
        } 
    } 

    pushResultIntoFiles(result) { 
            result.forEach(element => { 
                if (element.isFile) { 
                    this.files.push(element); 
                } else { 
                    this.pushResultIntoFiles(element); 
                } 
            }); 
    } 

    convertFileEntryToFile() { 
        var promises = []; 
        var tempFile; 
        this.files.forEach(fileEntry => { 
            tempFile = new Promise(resolve => { 
                fileEntry.file(file => { 
                    resolve(file); 
                }); 
            }); 
            promises.push(tempFile); 
        }); 
        Promise.all(promises).then(file => { 
            this.files = []; 
            this.files = file; 
        }); 
    } 

    handleCloseModal(){ 
        this.files=[]; 
        this.showFilePropertiesModal=false; 
        this.showDocumentPropertiesForm=false; 
    } 

    handleFileUpload(){ 
        //Write your file upload code. 
    }            
} 
Output
CONCLUSION
By using promises and file reader effectively in LWC, we should be able to get all the files from directory and its sub-directories.

If you have any questions you can reach out our Salesforce Consulting team here.

No comments:

Post a Comment