Серия статей "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 и другие полезные функции. И не забывайте, что очень желательно задавать файлам уникальные имена, т.к. при совпадении имён и расширений, старый файл будет попросту перезаписан новым.
"Замечтательно! - скажите вы. - А как же быть, если нужно загрузить несколько файлов одновременно?" Ничего сверхъестественного и существует несколько способов реализации:
- Используем атрибут multiple (в нашем случае, позволяет для одного поля input указывать несколько файлов). Добавляем этот атрибут в наше поле и изменяем его имя, добавив квадратные скобки "[]". Это укажет, что мы передаем массив данных из этого поля:
Способ хороший, но к сожалению не все браузеры с ним дружат.<input type="file" name="avatar[]" id="avatar" multiple>
- Заранее подготовить несколько полей, которым так же можно указать одинаковые имена массивом:
Это уже кроссбраузерный вариант, но и он имеет маленькие недостатки при определённых обстоятельствах. К примеру, мы создали два поля, а пользователю необходимо загрузить три и более файлов.<input type="file" name="avatar[]"> <input type="file" name="avatar[]">
- Динамическое добавление полей. Тут мы создаем одно поле и добавляем какую-нибуть кнопку, по нажатию на которую, пользователь сможет добавлять необходимое кол-во дополнительный полей. Каким способом добавлять поля - дело вашего вкуса и фантазии. Можно заранее подготовить код поля в 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, без использования фреймворков.