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