Перетаскивание файлов без AJAX, синхронно на переднем плане?

У меня есть веб-сайт с регулярной загрузкой файла <input type="file">, POSTing данные на бэкэнд при отправке формы.

Я хотел бы постепенно расширять форму, чтобы вы могли выгружать файл извне браузера в любом месте видового экрана (а не только в поле ввода файла, как встроено в некоторые браузеры), чтобы загрузить его.

Независимо от того, является ли автосогласование формы неважным. Поэтому, если drag-and-drop выбирает только файл в поле файла, без запуска загрузки, это прекрасно. Мне не нужна поддержка нескольких файлов. Мне не нужно показывать прогресс загрузки, эскизы или что-то фантастическое.

Я знаю, что есть JS-библиотеки, поддерживающие перетаскивание, но все они загружаются через AJAX. Я мог бы это сделать, но тогда мне нужно будет изменить бэкэнд и интерфейс, чтобы обрабатывать ошибки загрузки, перенаправлять и показывать правильные сообщения об успехе и т.д.

Я хочу прогрессивное усовершенствование, которое не требует каких-либо бэкэнд-изменений. Это должно происходить синхронно, используя форму на странице. JS в порядке, пока загрузка происходит "на переднем плане". Разумеется, синхронный AJAX не будет работать.

Ответы

Ответ 1

Хотя на самом деле это не "синхронно" (выполнение JavaScript фактически не останавливается), вы можете установить файлы, выбранные <input type="file"> программно. Фактически, такие элементы и перетаскивание разделяют их реализацию бэкэнд файла (File и FileList экземпляры), так что это действительно прямолинейно. Что еще, из-за обоих интерфейсов с использованием FileList s, перетаскивание нескольких файлов работает так же легко.

Это работает в Chrome (с использованием jQuery): http://jsfiddle.net/qMmPr/.

$(document).on("dragover drop", function(e) {
    e.preventDefault();  // allow dropping and don't navigate to file on drop
}).on("drop", function(e) {
    $("input[type='file']")
        .prop("files", e.originalEvent.dataTransfer.files)  // put files into element
        .closest("form")
          .submit();  // autosubmit as well
});

Ответ 2

Благодаря комментарию @pimvdb, я придумал довольно элегантное решение.

Так как перетаскивание на <input type="file" /> работает, почему бы не сделать его полноэкранным на dragstart, чтобы убедиться, что пользователь не может его пропустить? В любом случае он тащит, так что его намерения в этот момент ясны.

Вот демо: https://jsfiddle.net/08wbo4um

NB: к сожалению, это не работает в iframe, но работает на реальной странице. Вы все еще можете понять поведение.

Здесь фрагмент:

  $('input[type="file"]').on('change', function(e){
    var fileName = e.target.files[0].name;
    if (fileName) {
      $(e.target).parent().attr('data-message', fileName);
    }
  });
  
  $(document).on('drag dragstart dragend dragover dragenter dragleave drop', function(e) {
    if ($('input[type="file"]').length) {
      if (['dragover', 'dragenter'].indexOf(e.type) > -1) {
        if (window.dragTimeout)
          clearTimeout(window.dragTimeout);
        $('body').addClass('dragged');
      } else if (['dragleave', 'drop'].indexOf(e.type) > -1) {
        // Without the timeout, some dragleave events are triggered
        // when the :after appears, making it blink...
        window.dragTimeout = setTimeout(function() {
          $('body').removeClass('dragged');
        }, 100);
      }
    }
  });
h3, p {
  text-align: center;
}

.form-group {
  margin: 30px;
}

.file-upload .form-control {
  height: 150px;
  outline: 1px dashed #ccc;
  outline-offset: -15px;
  background-color: #eee;
}
.file-upload .form-control:before {
  content: "\f093";
  font: normal normal normal 14px/1 FontAwesome;
  font-size: 3em;
  left: 0;
  right: 0;
  display: block;
  margin: 20px auto;
  text-align: center;
}
.file-upload .form-control:after {
  content: attr(data-message);
  left: 0;
  right: 0;
  bottom: 0;
  text-align: center;
  display: block;
}
.file-upload .form-control input[type="file"] {
  cursor: pointer;
  opacity: 0;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
}
body.dragged .file-upload .form-control input[type="file"] {
  /* Make sure it is full screen, whatever the position absolute container */
  position: fixed;
  top: -50vh;
  bottom: -50vh;
  left: -50vw;
  right: -50vw;
  height: 200vh;
  width: 200vw;
  z-index: 10002;
}

body:after {
  content: 'You can drop the file. :-)';
  font-size: 2em;
  text-align: center;
  line-height: 100vh;
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: 10px;
  right: 10px;
  background-color: #eee;
  z-index: 10000;
  border-radius: 4px;
  border: thin solid #ccc;
  visibility: hidden;
  opacity: 0;
  transition: visibility 0s, opacity 0.5s ease;
}

body.dragged:after {
  opacity: 1;
  visibility: visible;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

<h3>Drag N Drop file upload without AJAX Demo</h3>
<p>Try drag and dropping a file. :-)</p>

<div class="form-group file-upload" required="required">
    <label class="cols-sm-2 control-label" for="document_file">File Upload</label><br>
    <div class="cols-sm-10">
      <div class="input-group">
        <span class="input-group-addon"><i class="fa fa-file" aria-hidden="true"></i></span>
        <div class="form-control" data-message="Click to select file or drag n drop it here">
          <input required="required" title="Click to select file or drag n drop it here" type="file" name="document[file]" id="document_file">
        </div>
      </div>
    </div>
  </div>

Ответ 3

Это можно сделать, превратив autoUpload в false, собирая файлы в массиве, затем в форме submit выполните один вызов ajax со всеми файлами вместе с данными формы, как описано .