如何使用更改和删除事件在 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">元素处设置webkitdirectoryallowdirs属性;将changedrop 事件附加到<input type="file"> 元素;在 mozilla 中使用 .getFilesAndDirectories().createReader().readEntries() webkitArray.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 中通过按钮单击事件上的另一个工具提示文本更改工具提示文本

删除事件 dataTransfer.getData() 在 Firefox 上返回 ""

如何在 Firefox / Firebug 中美化 JavaScript 和 CSS?