如何使用更改和删除事件在 firefox 和 chrome/chromium 上上传和列出目录
Posted
技术标签:
【中文标题】如何使用更改和删除事件在 firefox 和 chrome/chromium 上上传和列出目录【英文标题】:How to upload and list directories at firefox and chrome/chromium using change and drop events 【发布时间】:2017-02-01 13:03:04 【问题描述】:mozilla 和 webkit 浏览器现在都允许上传目录。当在<input type="file">
元素处选择一个或多个目录或在一个元素处拖放时,如何在firefox和chrome/chromium中按照它们在实际目录中出现的顺序列出所有目录和文件,并在所有上传目录时对文件执行任务迭代了吗?
【问题讨论】:
你还缺少什么信息? @KScandrett 没有关于从 firefox 或 chromium 的目录创建文件数组,这是我知道的。 Answer 中的listDirectory()
和listFile()
函数可能仍需要改进,以便在每个浏览器中获取和列出准确的结果。上次处理这些函数时,如果没记错的话,要获得文件对象的正确父目录是一项挑战,其中包含文件的目录也包含目录。 Answer 有一个TODO
,主要尝试了chromium的listDirectories()
和listFiles()
@KScandrett 如果您开发了与当前答案不同的方法来满足要求,请发布您的答案
@KScandrett 决定不选择“要求的规范答案”,这可能是适用的,因为已经编写了自己的答案来获取文件作为问题的单个数组部分;潜在地查看其他 SO 查看器和用户如何处理将文件作为单个数组获取并在 html 中创建呈现的目录和文件树的要求,从而在 chromium/chrome 和 firefox 浏览器中一致地准确反映上传的文件夹的目录树。
【参考方案1】:
您可以在<input type="file">
元素处设置webkitdirectory
和allowdirs
属性;将change
、drop
事件附加到<input type="file">
元素;在 mozilla 中使用 .getFilesAndDirectories()
,.createReader()
,.readEntries()
webkit
,Array.prototype.reduce()
,Promise
,递归。
注意 firefox drop
事件不会将选择列为Directory
,而是将File
对象列为具有size
0
的对象,因此在firefox 中删除目录不提供已删除文件夹的表示,即使在@使用 987654340@。当设置allowdirs
属性时,firefox 还提供了两个输入元素;第一个元素允许上传单个文件,第二个元素允许上传目录。 chrome/chromium 提供单个 <input type="file">
元素,其中只能选择单个或多个目录,而不是单个文件。
在同时包含文件和目录的目录中,首先读取目录。
<!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>
plnkrhttp://plnkr.co/edit/ff5vmuuIMzSapu6MiUJ3?p=preview
【讨论】:
见File and Directory Entry API以上是关于如何使用更改和删除事件在 firefox 和 chrome/chromium 上上传和列出目录的主要内容,如果未能解决你的问题,请参考以下文章
如何在Firefox上的“拖动”事件处理程序中使用clientX和clientY?
Firefox:使用 transform3d 转换父 div 时,下拉菜单(<select> 元素)不注册“更改”事件?
在 xul 和 javascript 中通过按钮单击事件上的另一个工具提示文本更改工具提示文本