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. } }
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.