Автор Тема: HTML5 File API: множественная загрузка файлов на сервер  (Прочитано 8269 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн qwertyuser

  • самый главный админ
  • Administrator
  • Hero Member
  • *****
  • Сообщений: 560
  • Karma: +65535/-0
  • кармадрочер
    • Просмотр профиля
    • ufoserver.org
Когда передо мной в очередной раз встала задача об одновременной загрузке нескольких файлов на сервер (без перезагрузки страницы, само собой), я стал блуждать по интернетам в поисках довольно корявого jQuery-плагина, который позволяет имитировать ajax-загрузку файла (того самого плагина, который со скрытым фрэймом: от java- и flash- плагинов сразу было решено отказаться). В процессе поиска я вспомнил, что в грядущем стандарте html 5 возможности по работе с файлами должны быть существенно расширены, и часть этих возможностей доступна уже сейчас. В итоге было решено опробовать их в действии.

Рассматривать возможности File API будем на примере одновременной загрузки нескольких картинок на сервер.

Итак, какие же преимущества дает нам использование File API:
Независимость от внешних плагинов
Возможность контролировать процесс загрузки и отображать информацию о нем (прогрессбар всегда добавляет терпения пользователю)
Возможность прочитать файл и узнать его размер до начала загрузки (в нашем примере это дает нам возможность отсеять файлы, не содержащие изображений и показывать миниатюры картинок)
Возможность выбрать сразу несколько файлов через стандартное поле выбора файла
Возможность использовать интерфейс drag and drop для выбора файлов. Да-да, мы сможем перетаскивать файлы для загрузки прямо с рабочего стола или, например, из проводника!

Из недостатков можно отметить только недостаточную поддержку в браузерах. Сейчас File API поддерживают только Firefox ≥ 3.6 и Chrome ≥ 6.0. Есть такое ощущение, что и Safari уже совсем скоро подтянется, а вот про IE и Opera пока ничего не ясно (может быть, кто-то располагает сведениями?). Расстроило конечно, что File API не поддерживает IE9 Beta: это странно, учитывая что разработчики IE сейчас взяли курс на обильную поддержку html 5. Но как бы то ни было, очевидно, что в будущем всем браузерам придется подтянуться.

Работающий пример можно увидеть по адресу http://safron.su/playground/html5uploader/, ниже приведены только наиболее важные фрагменты кода.

Для начала разберемся с html-кодом. Нам понадобится дефолтный элемент input, контейнер для перетаскивания файлов и список ul, куда мы будем помещать миниатюрки изображений:
<div>
 <input type="file" name="file" id="file-field" multiple="true" />
</div>
<div id="img-container">
 <ul id="img-list"></ul>
</div>
* This source code was highlighted with [url=http://virtser.net/blog/post/source-code-highlighter.aspx]Source Code Highlighter.[/url]

Ничего особенного, кроме того, что для элемента input указан атрибут multiple="true". Это необходимо для того, чтобы в стандартном диалоге выбора файлов можно было выделять их сразу несколько. Кстати, начиная с Firefox 4, разработчики браузера обещают, что ненавистные многим верстальщикам стандартные поля выбора файла можно будет скрывать, а диалог показывать, вызвав событие click для скрытого элемента.

Теперь перейдем к JavaScript (обратите внимание, что я использовал jQuery для упрощения манипуляций с DOM. Тот, кто по каким-либо причинам захочет отказаться от jQuery, сможет без труда переделать скрипты таким образом, чтобы обойтись без него). Сначала сохраним в переменных ссылки на html-элементы, снявшиеся в главных ролях. Далее определим обработчики событий для стандартного поля выбора файлов и для области, куда можно будет перетаскивать файлы.
  // Стандарный input для файлов
  var fileInput = $('#file-field');
 
  // ul-список, содержащий миниатюрки выбранных файлов
  var imgList = $('ul#img-list');
 
  // Контейнер, куда можно помещать файлы методом drag and drop
  var dropBox = $('#img-container');
 
  // Обработка события выбора файлов в стандартном поле
  fileInput.bind({
    change: function() {
      displayFiles(this.files);
    }
  });
     
  // Обработка событий drag and drop при перетаскивании файлов на элемент dropBox
  dropBox.bind({
    dragenter: function() {
      $(this).addClass('highlighted');
      return false;
    },
    dragover: function() {
      return false;
    },
    dragleave: function() {
      $(this).removeClass('highlighted');
      return false;
    },
    drop: function(e) {
      var dt = e.originalEvent.dataTransfer;
      displayFiles(dt.files);
      return false;
    }
  });
* This source code was highlighted with [url=http://virtser.net/blog/post/source-code-highlighter.aspx]Source Code Highlighter.[/url]

И в том и в другом случае в обработчике мы получаем доступ к объекту FileList, который по сути представляет собой массив объектов File. Этот массив передается функции displayFiles(), текст которой приведен ниже.

function displayFiles(files) {
    $.each(files, function(i, file) {     
      if (!file.type.match(/image.*/)) {
        // Отсеиваем не картинки
        return true;
      }           
      // Создаем элемент li и помещаем в него название, миниатюру и progress bar,
      // а также создаем ему свойство file, куда помещаем объект File (при загрузке понадобится)
      var li = $('<li/>').appendTo(imgList);
      $('<div/>').text(file.name).appendTo(li);
      var img = $('<img/>').appendTo(li);
      $('<div/>').addClass('progress').text('0%').appendTo(li);
      li.get(0).file = file;
 
      // Создаем объект FileReader и по завершении чтения файла, отображаем миниатюру и обновляем
      // инфу обо всех файлах
      var reader = new FileReader();
      reader.onload = (function(aImg) {
        return function(e) {
          aImg.attr('src', e.target.result);
          aImg.attr('width', 150);
          /* ... обновляем инфу о выбранных файлах ... */
        };
      })(img);
     
      reader.readAsDataURL(file);
    });
  }
* This source code was highlighted with [url=http://virtser.net/blog/post/source-code-highlighter.aspx]Source Code Highlighter.[/url]

Объект File содержит метаданные о файле, такие как его имя, размер и тип (в формате MIME, например, image/gif) соответственно в свойствах name, size и type. Для доступа же к содержимому файла существует специальный объект FileReader.

Внутри функции displayFiles() мы проходимся по переданному массиву файлов и сначала отсеиваем те, которые не являются изображениями. Далее для каждого изображения создается элемент списка li, куда помещается пустой пока элемент img (обратите внимание, что в кажом элементе li также создается свойство file, содержащее соответствующий объект). После чего создается экземпляр FileReader и для него определяется обработчик onload, в котором данные передаются прямо в атрибут src созданного ранее элемента img. Метод readAsDataURL() объекта FileReader принимает параметром объект File и запускает чтение данных из него. В результате для всех выбранных через стандартное поле или перетащенных прямо в браузер картинок, мы видим их миниатюры (искусственно уменьшенные до 150 пикселей).

Что еще осталось сделать? Осталось только реализовать саму загрузку всех выбранных файлов на сервер. Для этого создадим какую-нибудь кнопку или ссылку, при нажатии на которую останется только пробежаться по всем созданным элементам li, прочитать их свойство file и передать в функцию uploadFile(), текст которой приведен ниже. Отмечу, что здесь для упрощения я реализовал загрузку через функцию, а в реальном примере, расположенном по адресу http://safron.su/playground/html5uploader/, я собрал все действия по загрузке в объект uploaderObject, при создании которого можно передать дополнительные параметры, такие как функции обратного вызова для получения информации о процессе загрузки.
function uploadFile(file, url) {
 
  var reader = new FileReader();
 
  reader.onload = function() {   
    var xhr = new XMLHttpRequest();   
   
    xhr.upload.addEventListener("progress", function(e) {
      if (e.lengthComputable) {
        var progress = (e.loaded * 100) / e.total;
        /* ... обновляем инфу о процессе загрузки ... */
      }
    }, false);
   
    /* ... можно обрабатывать еще события load и error объекта xhr.upload ... */
 
    xhr.onreadystatechange = function () {
      if (this.readyState == 4) {
        if(this.status == 200) {
          /* ... все ок! смотрим в this.responseText ... */
        } else {
          /* ... ошибка! ... */
        }
      }
    };
   
    xhr.open("POST", url);
    var boundary = "xxxxxxxxx";   
    // Устанавливаем заголовки
    xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary);
    xhr.setRequestHeader("Cache-Control", "no-cache");   
    // Формируем тело запроса
    var body = "--" + boundary + "\r\n";
    body += "Content-Disposition: form-data; name='myFile'; filename='" + file.name + "'\r\n";
    body += "Content-Type: application/octet-stream\r\n\r\n";
    body += reader.result + "\r\n";
    body += "--" + boundary + "--";
 
    if(xhr.sendAsBinary) {
      // только для firefox
      xhr.sendAsBinary(body);
    } else {
      // chrome (так гласит спецификация W3C)
      xhr.send(body);
    }
  };
  // Читаем файл
  reader.readAsBinaryString(file);
}
* This source code was highlighted with [url=http://virtser.net/blog/post/source-code-highlighter.aspx]Source Code Highlighter.[/url]

Здесь создается экземпляр уже знакомого нам объекта FileReader, точно так же, как и выше; ему присваивается обработчик события onload, в котором создается XMLHttpRequest (к сожалению, пока нельзя воспользоваться ajax-интерфейсом jQuery, поскольку там еще не предусмотрена загрузка файлов). В XMLHttpRequest второй версии появилось свойство upload, содержащее объект-загрузчик, который может обрабатывать события progress, load и error (подробнее см. http://www.w3.org/TR/XMLHttpRequest2/#xmlhttprequesteventtarget). В примере выше показана только обработка события progress. Далее присваиваем обработчик завершения запроса самому реквесту (в отличие от событий объекта-загрузчика он вызывается уже тогда, когда все данные загружены и ответ от сервера получен), добавляем два дополнительных заголовка и формируем тело запроса, читая данные из свойства result объекта FileReader. После этого загрузка запускается. Отмечу только, что по нынешней спецификации W3C подразумевается, что метод send() объекта XMLHttpRequest может принимать в параметре бинарные данные, что успешно и реализовано в Google Chrome, однако в Firefox сделано по-своему, через особый метод sendAsBinary(). Поэтому перед началом отправки проверяем, определен ли метод sendAsBinary() в объекте реквеста, и, если да, используем его.

Вот, собственно, и все. С нетерпением ждем утверждения и распространения html 5!

взято отсюдова, на всякий случай, чтобы не потерялось
по вопросам размещения Ваших сайтов на этом сервере пишите info@qwertyuser.ru