Ajax на практике.

Загрузка файлов.

В прошлой статье, мы пробежались по нескольким основным методам для получения данных и их дальнейшей передаче AJAX-запросом. Теперь пришло время поговорить о том, как же можно загружать файлы с помощью AJAX. Еще до недавнего времени, способов загружать файлы без перезагрузки самой страницы, было не так уж и много (скрытый iframe, Flash). Они и сейчас используются по причине того, что еще остаются пользователи со старыми версиями браузеров, которых не коснулся прогресс. Но оглядываться назад не будем, посему шагаем в ногу со временем.

Рассмотрим, на мой взгляд, один из самых удобных способов для работы с файлами (и не только) - объект FormData. Пусть будет такая простенькая форма, для загрузки аватара пользователя:

HTML (файл index.html)

<form action="handler.php" method="post" id="my_form" enctype="multipart/form-data">
  <label for="fio">Ф.И.О:</label>
    <input type="text" name="fio" id="fio"><br>
  <label for="avatar">Аватар:</label>
    <input type="file" name="avatar" id="avatar"><br>
  <input type="submit" id="submit" value="Отправить">
</form>

Перейдем к JS-части. С полем "Ф.И.О" сложностей не будет и его используем только для наглядности того, что вместе с файлом, мы можем отправлять любые другие данные.

jQuery (файл script.js)

$(function(){
  $('#my_form').on('submit', function(e){
    e.preventDefault();
    var $that = $(this),
    formData = new FormData($that.get(0)); // создаем новый экземпляр объекта и передаем ему нашу форму (*)
    $.ajax({
      url: $that.attr('action'),
      type: $that.attr('method'),
      contentType: false, // важно - убираем форматирование данных по умолчанию
      processData: false, // важно - убираем преобразование строк по умолчанию
      data: formData,
      dataType: 'json',
      success: function(json){
      	if(json){
          $that.replaceWith(json);
        }
      }
    });
  });
});

(*)Обратите внимание на то, что передаем форму не объектом jQuery, а DOM-элемент

PHP-обработчик (файл handler.php)

<?php
if(isset($_POST['fio'],$_FILES['avatar'])){
  $req = false; // изначально переменная для "ответа" - false
  // Приведём полученную информацию в удобочитаемый вид
  ob_start();
  echo '<pre>';
  echo 'Имя пользователя: <strong>' , $_POST['fio'] , '</strong><br>Данные загруженного файла:<br>';	
  print_r($_FILES['avatar']);
  echo '</pre>';
  $req = ob_get_contents();
  ob_end_clean();
  echo json_encode($req); // вернем полученное в ответе
  exit;
}

Если всё было сделано правильно, то на экране нам выведится информация в таком виде:

Имя пользователя: Alex
Данные загруженного файла:
Array
(
  [name] => avatar.jpg
  [type] => image/jpeg
  [tmp_name] => E:\OpenServer\userdata\temp\php8627.tmp
  [error] => 0
  [size] => 6697
)

Ясное дело, что выводить эту информацию нам не потребуется, а нужно будет сохранять файл на сервере, указав ему место "постоянной прописки" ;). Как загружать/сохранять файлы средствами php, я описывать тут не буду, т.к. тема не маленькая и ей можно посветить отдельную статью. Для самых нетерпеливых, могу дать пару намёков - используем: move_uploaded_file(), getimagesize(), Fileinfo и другие полезные функции. И не забывайте, что очень желательно задавать файлам уникальные имена, т.к. при совпадении имён и расширений, старый файл будет попросту перезаписан новым.

"Замечтательно! - скажите вы. - А как же быть, если нужно загрузить несколько файлов одновременно?" Ничего сверхъестественного и существует несколько способов реализации:

  1. Используем атрибут multiple (в нашем случае, позволяет для одного поля input указывать несколько файлов). Добавляем этот атрибут в наше поле и изменяем его имя, добавив квадратные скобки "[]". Это укажет, что мы передаем массив данных из этого поля:
    <input type="file" name="avatar[]" id="avatar" multiple>
    Способ хороший, но к сожалению не все браузеры с ним дружат.
  2. Заранее подготовить несколько полей, которым так же можно указать одинаковые имена массивом:
    <input type="file" name="avatar[]">
    <input type="file" name="avatar[]">
    Это уже кроссбраузерный вариант, но и он имеет маленькие недостатки при определённых обстоятельствах. К примеру, мы создали два поля, а пользователю необходимо загрузить три и более файлов.
  3. Динамическое добавление полей. Тут мы создаем одно поле и добавляем какую-нибуть кнопку, по нажатию на которую, пользователь сможет добавлять необходимое кол-во дополнительный полей. Каким способом добавлять поля - дело вашего вкуса и фантазии. Можно заранее подготовить код поля в JS, присвоив его переменной, можно клонировать уже существующий элемент - clone() и т.д.

Во всех трёх вариантах, на стороне сервера, мы получим массив файлов и, как с любым другим массивом, обрабатываем его в цикле.

Ещё одним моментом, который сто́ит затронуть - это добавление файлов (и других данных) в FormData, если формы, как таковой, нету или данные берутся из других источников. Для этой задачи, будем использовать метод append(), который похож на jQuery-метод append(), но выполняет немного другой функционал. Синтаксис метода:

void append(DOMString name, File value, optional DOMString filename);
void append(DOMString name, Blob value, optional DOMString filename);
void append(DOMString name, DOMString value);
Где параметры:
name
Имя поля, данные которого передаются в параметре value. По сути, если мы взяли данные не из поля формы, у которого есть атрибут name, то мы этот name задаём сами.
value
Значение поля. Может быть типа Blob, File или string
filename
Необязательный параметр. Для типов Blob и File - имя файла, сообщаемое серверу

Возьмем нашу форму загрузки аватара без изменений и предположим, что мы хотим добавим к ней текущую дату. Теперь наш код должен выглядеть следующим образом:

$(function(){
  $('#my_form').on('submit', function(e){
    e.preventDefault();
    var $that = $(this),
    formData = new FormData($that.get(0));
    formData.append('date_upl', new Date()); // добавляем данные, не относящиеся к форме. У нас - это дата
    $.ajax({
      url: $that.attr('action'),
      type: $that.attr('method'),
      contentType: false,
      processData: false,
      data: formData,
      dataType: 'json',
      success: function(json){
      	if(json){
          $that.replaceWith(json);
        }
      }
    });
  });
});

Вуаля, собственно новые данные добавлены к набору передавемых на сервер. Наша дата будет в переменной $_POST['date_upl']. Для добавления еще одной и более пары "ключ - значение", используем ту же конструкцию. С файлами дело обстоит так же, но нужно учитывать, что значением является DOM-элемент, а не объект jQuery. И напомню, что абсолютно все данные, приходящие от клиента, должны в обязательном порядке подвергаться проверке, фильтрации, валидации и т.д.!


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

Incode Pro logo

51 комментарий

Страница 3 из 3  
sash 11.12.2014 19:14
А у меня просто без [] получается
<td><input type="file" name="filename" multiple="true"/></td>
Страница 3 из 3  
Ваш комментарий:
X