现在,mozilla和webkit浏览器都允许目录上传。当在<input type="file">元素上选择目录或在元素上删除目录时,如何以顺序显示所有目录和文件的顺序,使其出现在firefox和chrome / chromium的实际目录中,并在迭代所有上载目录后对文件执行任务?
<input type="file">
您可以在element 处设置webkitdirectory和allowdirs属性<inputtype="file">;附change,drop事件<inputtype="file">元件;使用.getFilesAndDirectories()Mozilla的,.createReader(),.readEntries()在webkit,Array.prototype.reduce(),Promise,递归。
webkitdirectory
allowdirs
<inputtype="file">
change
drop
.getFilesAndDirectories()
.createReader()
.readEntries()
webkit
Array.prototype.reduce()
Promise
请注意,在firefox drop事件中不会将选择列为Directory,而是具有的File对象size 0,因此,即使在event.dataTransfer.getFilesAndDirectories()使用firefox时,在firefox处放置目录也无法提供所放置文件夹的表示。allowdirs设置属性时,firefox还提供了两个输入元素。第一个元素允许单个文件上传,第二个元素允许目录上传。chrome/ chromium提供单个<inputtype="file">元素,其中只能选择单个或多个目录,而不能选择单个文件。
Directory
File
size
0
event.dataTransfer.getFilesAndDirectories()
在同时包含文件和目录的目录中,首先读取目录。
<!DOCTYPE html> <html> <head> <style type="text/css"> input[type="file"] { width: 98%; height: 180px; } label[for="file"] { width: 98%; height: 180px; } .area { display: block; border: 5px dotted #ccc; text-align: center; } .area:after { display: block; border: none; white-space: pre; content: "Drop your files or folders here!\aOr click to select files folders"; position: relative; left: 0%; top: -75px; text-align: center; } .drag { border: 5px dotted green; background-color: yellow; } #result ul { list-style: none; margin-top: 20px; } #result ul li { border-bottom: 1px solid #ccc; margin-bottom: 10px; } #result li span { font-weight: bold; color: navy; } </style> </head> <body> <label id="dropArea" class="area"> <input id="file" type="file" directory allowdirs webkitdirectory/> </label> <output id="result"> <ul></ul> </output> <script> var dropArea = document.getElementById("dropArea"); var output = document.getElementById("result"); var ul = output.querySelector("ul"); function dragHandler(event) { event.stopPropagation(); event.preventDefault(); dropArea.className = "area drag"; } function filesDroped(event) { var webkitResult = []; var mozResult = []; var files; console.log(event); event.stopPropagation(); event.preventDefault(); dropArea.className = "area"; // do mozilla stuff // TODO adjust, call `listDirectory()`, `listFile()` function mozReadDirectories(entries, path) { console.log("dir", entries, path); return [].reduce.call(entries, function(promise, entry) { return promise.then(function() { return Promise.resolve(entry.getFilesAndDirectories() || entry) .then(function(dir) { return dir }) }) }, Promise.resolve()) .then(function(items) { var dir = items.filter(function(folder) { return folder instanceof Directory }); var files = items.filter(function(file) { return file instanceof File }); if (files.length) { // console.log("files:", files, path); files.forEach(function(file) { console.log(file) }); mozResult = mozResult.concat.apply(mozResult, files); } if (dir.length) { // console.log(dir, dir[0] instanceof Directory); return mozReadDirectories(dir, dir[0].path || path); } else { if (!dir.length) { return Promise.resolve(mozResult).then(function(complete) { return complete }) } } }) }; function handleEntries(entry) { let file = "webkitGetAsEntry" in entry ? entry.webkitGetAsEntry() : entry return Promise.resolve(file); } function handleFile(entry) { return new Promise(function(resolve) { if (entry.isFile) { entry.file(function(file) { listFile(file, entry.fullPath).then(resolve) }) } else if (entry.isDirectory) { var reader = entry.createReader(); reader.readEntries(webkitReadDirectories.bind(null, entry, handleFile, resolve)) } else { var entries = [entry]; return entries.reduce(function(promise, file) { return promise.then(function() { return listDirectory(file) }) }, Promise.resolve()) .then(function() { return Promise.all(entries.map(function(file) { return listFile(file) })).then(resolve) }) } }) function webkitReadDirectories(entry, callback, resolve, entries) { console.log(entries); return listDirectory(entry).then(function(currentDirectory) { console.log(`iterating ${currentDirectory.name} directory`, entry); return entries.reduce(function(promise, directory) { return promise.then(function() { return callback(directory) }); }, Promise.resolve()) }).then(resolve); } } // TODO: handle mozilla directories, additional directories being selected dropped and listed without // creating nested list at `html` where different directory selected or dropped function listDirectory(entry) { console.log(entry); var path = (entry.fullPath || entry.webkitRelativePath.slice(0, entry.webkitRelativePath.lastIndexOf("/"))); var cname = path.split("/").filter(Boolean).join("-"); console.log("cname", cname) if (!document.getElementsByClassName(cname).length) { var directoryInfo = `<li><ul class=${cname}> <li> <span> Directory Name: ${entry.name}<br> Path: ${path} <hr> </span> </li></ul></li>`; var curr = document.getElementsByTagName("ul"); var _ul = curr[curr.length - 1]; var _li = _ul.querySelectorAll("li"); if (!document.querySelector("[class*=" + cname + "]")) { if (_li.length) { _li[_li.length - 1].innerHTML += `${directoryInfo}`; } else { _ul.innerHTML += `${directoryInfo}` } } else { ul.innerHTML += `${directoryInfo}` } } return Promise.resolve(entry); } // TODO: handle mozilla files function listFile(file, path) { path = path || file.webkitRelativePath || "/" + file.name; var filesInfo = `<li> Name: ${file.name}</br> Size: ${file.size} bytes</br> Type: ${file.type}</br> Modified Date: ${file.lastModifiedDate}<br> Full Path: ${path} </li>`; var currentPath = path.split("/").filter(Boolean); currentPath.pop(); var appended = false; var curr = document.getElementsByClassName(`${currentPath.join("-")}`); if (curr.length) { for (li of curr[curr.length - 1].querySelectorAll("li")) { if (li.innerHTML.indexOf(path.slice(0, path.lastIndexOf("/"))) > -1) { li.querySelector("span").insertAdjacentHTML("afterend", `${filesInfo}`); appended = true; break; } } if (!appended) { curr[curr.length - 1].innerHTML += `${filesInfo}`; } } console.log(`reading ${file.name}, size: ${file.size}, path:${path}`); webkitResult.push(file); return Promise.resolve(webkitResult) }; function processFiles(files) { Promise.all([].map.call(files, function(file, index) { return handleEntries(file, index).then(handleFile) })) .then(function() { console.log("complete", webkitResult) }) .catch(function(err) { alert(err.message); }) } if ("getFilesAndDirectories" in event.target) { return (event.type === "drop" ? event.dataTransfer : event.target).getFilesAndDirectories() .then(function(dir) { if (dir[0] instanceof Directory) { console.log(dir) return mozReadDirectories(dir, dir[0].path || path) .then(function(complete) { console.log("complete:", complete); event.target.value = null; }); } else { if (dir[0] instanceof File && dir[0].size > 0) { return Promise.resolve(dir) .then(function(complete) { console.log("complete:", complete); }) } else { if (dir[0].size == 0) { throw new Error("could not process '" + dir[0].name + "' directory" + " at drop event at firefox, upload folders at 'Choose folder...' input"); } } } }).catch(function(err) { alert(err) }) } // do webkit stuff if (event.type === "drop" && event.target.webkitdirectory) { files = event.dataTransfer.items || event.dataTransfer.files; } else if (event.type === "change") { files = event.target.files; } if (files) { processFiles(files) } } dropArea.addEventListener("dragover", dragHandler); dropArea.addEventListener("change", filesDroped); dropArea.addEventListener("drop", filesDroped); </script> </body> </html>