【问题标题】:Drag,Drop and Submit files to upload拖放和提交文件以上传
【发布时间】:2015-12-22 05:23:53
【问题描述】:

我在 asp.net mvc 5 中实现 拖放 文件,例如 this, 但我的要求是当我拖动文件时,不应该立即上传。 首先拖动文件, 然后单击按钮(“输入元数据”) 为每个文件输入一些强制性属性(元数据名称、类别等...), 然后点击其他按钮(提交)提交上传。

通常,当我们拖动文件时,它会立即上传,我必须停止它并在单击按钮时执行(在填写其他字段后)。甚至任何具有类似功能的第三方 js 库?

我搜索了很多,但没有得到预期的结果。 有人可以指导我如何满足此要求或提供一些链接来满足此要求。

【问题讨论】:

    标签: javascript jquery asp.net asp.net-mvc drag-and-drop


    【解决方案1】:

    你试过 Dropzone.js http://www.dropzonejs.com/ 他们似乎支持您正在寻找的功能。 以下是相关页面的摘录。我自己没有尝试过,但纯粹基于文档值得尝试检查它是否符合您的要求。

    如果您禁用了 autoProcessQueue,则需要调用 .processQueue()你自己。

    如果您想显示文件并让用户 单击接受按钮以实际上传文件。

    【讨论】:

    • 是的,我有,但是我们必须根据问题来回答:(这是一个非常好的库,我已经使用它了。
    • @AmirHosseinMehrvarzi 如果我正确理解您的问题,我看到您想要并回答说如何使用 filedrop.js 或什至任何具有类似功能的第三方 js 库?我的回答基本上是说您使用 dropzone.js 而不是 filedrop.js,而且您似乎已经使用过它。如果您正在寻找“仅”使用 jquery-filedrop.js 的解决方案,请改写您的问题。
    • @AmirHosseinMehrvarzi 刚刚意识到 OP 是另一个人。 OP 要么想要一个说明如何使用 filedrop.js 的答案,要么甚至是任何具有类似功能的第三方 js 库?我的回答基本上是说您使用 dropzone.js 而不是 filedrop.js,因为它具有默认可用的感兴趣的功能。看来您已经使用它了,所以我认为您可以为答案添加更多颜色。如果 OP 正在寻找“仅”使用 jquery-filedrop.js 的解决方案,我建议将问题改写为这样。
    • 请不要发表评论两次!
    • 很抱歉。错过了编辑我的评论的时间窗口,最后评论了两次。
    【解决方案2】:

    由于您正在寻找一个库,我为您开发了一个完整的 vanilla Javascript 解决方案,如下所示:

    /* as soon as all the elements of the page has been loaded */
    document.addEventListener('DOMContentLoaded', function() {
      /* we reference the most used elements into variables, making it easier to use later */
      var dropzone = document.getElementById('dropzone'),
          dialog = document.getElementById('dropzone-dialog'),
          submit = document.getElementById('submit'),
          progress = document.querySelector('progress'),
          _current = null; // we keep track what file the user is changing its metadata
    
      /* when the user drags something over our div, we must prevent the browser's default
         behavior and we change a CSS class only for usability reasons */
      dropzone.addEventListener('dragover', function(e) {
        e.preventDefault();
        dropzone.classList.add('dragging');
      });
    
      /* when the user leaves our div, we must remove the CSS class we put before */
      dropzone.addEventListener('dragleave', function(e) {
        e.preventDefault();
        dropzone.classList.remove('dragging');
      });
    
      /* that's the heart of our code,
         when the user effectively drops one or more files into the div */
      dropzone.addEventListener('drop', function(e) {
        /* again we must prevent the default browser's behavior,
           and we need to remove the CSS clas we put before */
        e.preventDefault();
        dropzone.classList.remove('dragging');
        submit.classList.add('hidden');
    
        /* we change our div's text */
        dropzone.textContent = 'File(s) dropped:';
    
        /* we create an UL element, so we can loop through the files make a list */
        var ul = document.createElement('ul');
        /* since the files property is not an array, but it is Array-like,
           we call the Array.prototype.forEach method passing the files to be the context */
        [].forEach.call(e.dataTransfer.files, function(file) {
          /* for each file the user has dropped, we create a LI and an anchor inside it */
          var li = document.createElement('li');
          var a = document.createElement('a');
    
          a.href = '#';
          a.textContent = file.name;
          a.title = 'Enter metadata';
          
          /* we create a property __file into our anchor to reference the dropped file */
          a.__file = file;
    
          li.appendChild(a);
          ul.appendChild(li);
        });
        dropzone.appendChild(ul);
      });
    
      /* here, we add a 'click' event handler to our div dropzone,
         so we can add the file's metadata behavior.
         the idea here is to make an event delegation, that is, when the user clicks anywhere
         inside the div, we see if the element that was clicked is one of the files */
      dropzone.addEventListener('click', function(e) {
        var el = e.target;
    
        /* if the element clicked is an anchor, it's because it's one of the files */
        if (el.tagName === 'A') {
          /* we prevent the browser's default behavior */
          e.preventDefault();
          /* we keep track that the current clicked file/anchor is the last clicked */
          _current = el;
          
          /* and then, we show our dialog, so the user can input the file's metadata */
          dialog.classList.remove('hidden');
          /* we set focus on the first element of the dialog, only for usability reasons */
          dialog.querySelector('#name').focus(); 
          
        }
      });
    
      /* we add an event handler to the dialog's close button,
         so it can effectively close the dialog */
      dialog.querySelector('.close').addEventListener('click', clearDialog);
      
      /* this is the second heart of our code.
         we add an event handler to the dialog's save button,
         so it can handle the saving of metadata */
      dialog.querySelector('#save').addEventListener('click', function() {
        /* here you can add any validation you want, e.g: required fields, etc */
        
        /* if everything was cool, we set a new property __dataFile to our anchor
           so, we can save the metadata for future use (the form submission) */
        _current.__dataFile = {
          name: dialog.querySelector('#name').value,
          category: dialog.querySelector('#category').value
          /* put here any other metadata property you will save */
        };
        
        /* when the user saves the metadata, we add a CSS class only for usability reasons */
        _current.classList.add('finished');
    
        /* then, we keep track if there is any file with no metadata saved yet */
        var btnsLeft = [].filter.call(dropzone.querySelectorAll('a'), function(el, i) {
          return !('__dataFile' in el);
        });
    
        /* if all the files' metadatas have been saved, we show the submit button */
        if (btnsLeft.length === 0)
          submit.classList.remove('hidden');
    
        /* and we clear/close our dialog */
        clearDialog();
      });
    
      function clearDialog() {
        /* when the dialog is closed, we set our current-clicked anchor to null */
        _current = null;
    
        /* we clean all the input fields of the dialog */
        [].forEach.call(dialog.querySelectorAll('input'), function(el, i) {
          el.value = '';
        });
    
        /* and we effectively hide/close the dialog */
        dialog.classList.add('hidden');
      }
    
      /* this is third heart of our code.
         we add a click event handler to our submit button,
         so we can effectively send our form to the server */
      submit.querySelector('button').addEventListener('click', function(e) {
        e.preventDefault(); // we must prevent the browser's default behavior
    
        /* we create a XMLHttpRequest to send our AJAX to the server */
        var xhr = new XMLHttpRequest();
    
        /* we add a 'load' event handler, so we can keep track when the upload is finished */
        xhr.addEventListener('load', finished);
        /* we do the same for progress and error, so we can inform our user what's going on */
        xhr.upload.addEventListener('progress', progress);
        xhr.addEventListener('error', error);
        
        /* now it's time to save all our data, so we create a FormData */
        var fd = new FormData();
    
        /* and for each anchor(file) inside our dropzone, we add new data to our FormData */
        [].forEach.call(dropzone.querySelectorAll('a'), function(el, i) {
          /* here we loop through our __dataFile previously created property,
             so we can add the file's metadata parameter */
          for (var prop in el.__dataFile) {
            /* since we are sending multiple files/metadatas,
               it's important to name our field with [] */
            fd.append(prop + '[]', el.__dataFile[prop]);
          }
          
          /* and then, we append the file itself - again, with the [] at the end */
          fd.append('file[]', e.__file);
        });
    
        /* now we open the xhr, and we effectively send the request to the server */
        xhr.open('POST', '/api/uploadfiles/'); // change for your API URL
        xhr.send(fd);
      });
      
      /* this is the xhr's progress, so we can update our HTMLProgressElement's percent */
      function progress(e) {
        progress.value = progress.innerHTML = (e.loaded / e.total * 100 | 0);
      }
    
      /* this is the xhr's finished event handler,
         we show a friendly message and hide the submit button */
      function finished(e) {
        progress.value = progress.innerHTML = 100;
        dropzone.innerHTML = '<h3>Files successfully uploaded</h3>';
        submit.classList.add('hidden');
      }
      
      /* this is the xhr's error event handler. If there is any known error, we show it */
      function error(e) {
        var xhr = e.target;
        submit.classList.add('hidden');
        dropzone.innerHTML = '<h3>Error while uploading</h3>' +
          (xhr.status ? '<span>' + xhr.status + ' - ' + xhr.statusText + '</span>' : '');
      }
    });
    #dropzone {
      width: 450px;
      height: 165px;
      background-color: #CCC;
      font: 16px Verdana;
      padding: 10px;
      box-sizing: border-box;
    }
    #dropzone.dragging {
      background-color: #EEE;
    }
    #dropzone > h3 {
      text-align: center;
    }
    #dropzone a {
      text-decoration: none;
    }
    #dropzone a:hover {
      text-decoration: underline;
    }
    #dropzone a.finished:after {
      content: ' √';
      color: green;
      font-weight: bold;
      text-decoration: none;
    }
    #dropzone ul {
      list-style: none;
      margin-left: 15px;
      padding: 0;
    }
    #dropzone li {
      margin-top: 8px;
    }
    #dropzone li:before {
      content: '> ';
    }
    .hidden {
      display: none;
    }
    .dialog {
      background-color: #AAAAFF;
      font-family: Verdana;
      padding: 12px;
      border-radius: 10px;
      width: 300px;
      height: 150px;
      position: absolute;
      top: 20px;
    }
    .dialog h3 {
      text-align: center;
    }
    .dialog .close {
      text-decoration: none;
      float: right;
    }
    .dialog .close:after {
      content: '✖';
      cursor: pointer;
    }
    .dialog > div {
      display: table;
    }
    .dialog > div > div {
      display: table-row;
    }
    .dialog > div > div > div,
    .dialog > div > div > label {
      display: table-cell;
      padding: 2px 0 2px 10px;
    }
    
    #submit {
      height: 160px;
      width: 450px;
      text-align: right;
      position: fixed;
      top: 10px;
    }
    #submit button {
      font-size: 20px;
      padding: 10px;
      background-color: orange;
    }
    
    progress {
     width: 450px; 
    }
    <div id="dropzone">
      <h3>Drop your files here</h3>
    </div>
    <div>
      <progress min="0" max="100" value="0"></progress>
    </div>
    <div id="submit" class="hidden">
      <button>Submit</button>
    </div>
    <div id="dropzone-dialog" class="dialog hidden">
      <a class="close"></a>
      <h3>File Metadata</h3>
      <div>
        <div>
          <label for="name">
            Name
          </label>
          <div>
            <input id="name">
          </div>
        </div>
        <div>
          <label for="category">
            Category
          </label>
          <div>
            <input id="category">
          </div>
        </div>
        <div>
          <div></div>
          <div style="text-align: right">
            <button id="save">Save</button>
          </div>
        </div>
      </div>
    </div>

    以上代码已全部注释,但如果您需要任何帮助,请发表评论。

    【讨论】:

      【解决方案3】:

      根据我的研究,没有办法在 jquery.filedrop 中手动处理上传。您可以使用event functions 之一,如下面的prompt 用户输入一些额外的参数,如元数据等......然后将它们附加到发送数据中,如下所示:

      $('#myElement').fileDrop({
          data: {
              param1: 'value1',           // send POST variables
              param2: function(){
                  return calculated_data; // calculate data at time of upload
              },
          },
          onFileRead : function(fileCollection){
              $.each(fileCollection, function(){
                  //Do stuff with fileCollection here!
              });
          },
          drop: function() {
              // user drops file
          },
          beforeSend: function(file, i, done) {
              // file is a file object
              // i is the file index
              // call done() to start the upload
          },
          // Called before each upload is started
          beforeEach: function(file){
              //do some stuff here
          }
      });
      

      【讨论】:

      • 如果我错了,请见谅,这和我的回答不一样吗?
      • @enigma :) 你已经扩展了 filedrop 库。但是我使用事件来提示用户输入额外的数据。是不是真的?
      • 是的,您已经获得了prompt() 的链接。 MDN link,如果有人感兴趣的话。
      • 您可能想提及prompt() 在您的示例代码中实际使用的位置。或者把它作为练习留给读者:)
      • @enigma :) 你是对的。基本上我的答案有一个解决方案,但是您的答案更完整,其中包含库的扩展。
      【解决方案4】:

      您链接到的示例代码似乎使用的是由 Weixi Yen 编写的 jquery.filedrop.js。您需要从其project home 下载并使用最新版本才能正常工作。

      您还应该下载并使用比示例代码捆绑的更高版本的 jquery。我已经用 jquery 1.9.1 对此进行了测试。

      要使用您选择的 jquery 扩展,您需要利用 beforeSend 选项,并提供您自己的函数。您还需要为每个文件存储对提供给您的自定义函数的done() 函数的引用,以便您以后可以调用它们,从而导致文件被上传。

      如果您希望为每个文件显示元框,那么您需要在每个文件的基础上附加适当的 html 以允许用户填写它们。

      我建议的代码摘要如下:

      var uploads_to_call = [];  // the global to store all the file upload callbacks
      
      $('#dropzone').filedrop({
          fallback_id: 'upload_button',   // an identifier of a standard file input element, becomes the target of "click" events on the dropzone
          url: 'upload.php',              // upload handler, handles each file separately, can also be a function taking the file and returning a url
          // other important parameters related to upload, read the documentation for details
      
          // this is the important custom function you need to supply
          beforeSend: function(file, i, done) {
              // file is a file object
              // i is the file index
              // call done() to start the upload
      
              // this is just to clone meta boxes for the user to fill in for each file
              // it also fills in the filename so that it's obvious which meta boxes
              // are for which files
              $("#perfile").children().clone()
                 .appendTo("#allmeta")
                 .children(".filename").html(file.name);
      
              // this pushes the done() callback into the global mentioned earlier
              // so that we can call it later
              uploads_to_call.push(done);
          },
          afterAll: function() {
              // runs after all files have been uploaded or otherwise dealt with
              // you should possibly put code in here to clean up afterwards
          }
      });
      
      // set a handler to upload the files when the submit button is clicked
      $("#submit").click(function(){
          $.each(uploads_to_call, function(i, upcall) {
              upcall();
          });
      });
      

      与html类似如下:

      <form>
          <div id="dropzone"></div>
          <div id="allmeta"></div>
          <button id="submit">submit</button>
      </form>
      
      <div id="perfile">
          <div class="meta">
              <span class="filename"></span>
              <input type="text" placeholder="meta"/>
              <input type="text" placeholder="meta"/>
          </div>
      </div>
      

      div#perfile 应该有 css 来隐藏它,因为它只用于包含每次拖入文件时要克隆到表单中的 div。

      我创建了一个概念证明jsfiddle here,显然这实际上不允许上传文件,但它显示了 JS 方面的工作。您需要向下滚动到 javascript 面板的底部才能查看自定义代码 - 顶部的内容仅包括您应该从项目主页下载的 javascript 扩展。

      这应该足以让您在 asp.net 方面正常工作。您只需要以您认为合适的方式发布用户提供的其他元数据。

      显然这是准系统,您应该根据自己的需要充实它。

      【讨论】:

      • 什么是upcall?
      猜你喜欢
      • 1970-01-01
      • 2012-08-23
      • 1970-01-01
      • 2012-08-27
      • 1970-01-01
      • 2011-09-22
      • 2013-01-27
      相关资源
      最近更新 更多