Динамическая загрузка файлов на сервер с использованием AJAX
И снова здравствуйте. В первой статье на своем блоге я хотел рассказать про что-нибудь связанное с PHP, но пока я размышлял, про что бы написать, раздался звонок от коллеги по работе. У него возникли проблемы с организаций быстрой загрузки картинок на сервер, и он хотел, чтобы я ему помог, так как некоторое время назад сам реализовывал подобный функционал в проекте. С его проблемой мы справились быстро, он просто пропустил один пункт из документации по JS-фреймворку, который мы используем.
Но речь пойдет не конкретно об этом случае. В этой статье я хочу обобщить свой опыт решения проблемы по загрузке файлов с применением AJAX’а.
Начнем с того, что загрузка файлов происходит в обход объекта XMLHttpRequest, который в настоящее время используется для реализации технологии AJAX. Дело в том, что этот объект не умеет передавать файлы, поэтому, для решения нашей задачи, придется сделать небольшой шаг назад по истории развития AJAX – воспользоваться фреймом, который будет динамически создан скриптом. Технически, динамическая загрузка файла на сервер выглядит следующим образом:
1. При необходимости загрузки файла, скрипт создает невидимый фрейм;
2. В создается невидимая копия формы, в которой пользователь уже выбрал файл для загрузки (как правило, это форма содержит всего одни элемент input, которым, собственно, и выбирается файл), в качестве цели для которой указывается созданный фрейм;
3. Скрипт подтверждает форму и начинается отправка файла;
4. По завершения загрузки файла, ответ сервера записывается в тело фрейма, откуда забирается скриптом основной страницы и обрабатывается.
Реализовать это способ вы можете сами, если, по каким-либо причинам, у вас нет возможности воспользоваться готовыми JS-библиотеками. Ниже описано как динамически загрузить файл используя ExtJS, и приведен пример реализации загрузки файла без использования библиотек.
Начну с библиотеки ExtJS, так как для меня это основная библиотека, которую я использую при создании динамики в интерфейсе страницы. Эта библиотека имеет два варианта: ExtCore – облегченный вариант, который содержит набор классов для создания интерактивности на любой веб-страницы; ExtJS – расширенный вариант, который, в дополнение к базовому, включает в себя классы, для работы с данными и создания интерфейсов приложения. Библиотека является свободной и доступа на официальном сайте.
Для использования динамических запросов к серверу в этой библиотеке существует специальный класс – Ext.Ajax, который доступен в обоих вариантах библиотеки. Простейший вариант отправить AJAX-запрос с помощью ExtJS выглядит следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | //Листинг 1. Использования AJAX с помощью ExtJS/*** Отправка запроса серверу* @param params Объект с параметрами для передачи серверу* @return void*/function sendRequest (params){ var ajax = { 'url': '/ajax/request', // адрес для выполнения 'params': params, //строка или объект с параметрами 'success': processResponse, // Указатель на функцию для // обработки успешного ответа 'failure': requestFailure //Функция обработки ошибки }; Ext.Ajax.request(ajax); //отправка запроса} // sendRequest/*** Обработка ответа сервера при успешном выполнении запроса* @param response Объект XMLHttpRequest с ответом сервера* @param options Объект с конфигурацией отправленного запроса* @return void*/function processResponse (response, options){ //Покажем сообщение с текстом ответа сервера alert(response.responseText);} // processResponse/*** Функция, срабатывающа во случае не удачного выполнения запроса* @param response Объект XMLHttpRequest* @param options Объект с конфигурацией запроса* @return void*/function requestFailure(response, options){ alert('Что-то не в порядке');} // requestFilure |
Если обратиться к документации по ExtJS, то там сказано, что для отправки файла необходимо указать параметр запроса isUpload равным true, а параметр form должен быть равен идентификатору формы или являться указателем на объект формы. Кроме того, там указывается на то, что если сервер отвечает в формате JSON, то он должен вернуть ответ с заголовком Content-Type равным «text/html». Собственно, в этом и была ошибка моего коллеги, так как, по умолчанию, ответы на AJAX запросы возвращались с типом «application/json».
Ниже приведен пример html, js, и php файлов, реализующих динамическую загрузку файлов на сервер.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <!-- Листинг 2-1 Динамическая загрузка файлов на сервер (html) --><!-- Файл upload.html --><!DOCTYPE html><html><head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Динамическая загрузка файлов</title> <!-- Подключаем файл библиотеки и файл с нашими скриптами--> <script type="text/javascript" src="js/ext-core.js"></script> <script type="text/javascript" src="js/uploader.js"></script> <style> .x-hidden { display:none; } </style></head><body> <!-- Форма с единственным полем: выбор файла --> <form id="fileUploader"> <input type="file" name="newFile" id="input-file" onchange="uploadFile()" /> </form> <!-- Конец формы --> <!-- Таблица для вывода сообщений о загрузке --> <table id='upload-report' border="1"> <tr> <th>Локальное имя</th> <th>Имя на сервере</th> <th>Размер</th> <th>Тип</th> </tr> </table></body></html><!-- Файл upload.html --> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | //Листинг 2-2. Динамическая загрузка файлов на сервер (js)// Файл js/uploader.js/*** Отправка запроса на загрузку файла* @return void*/function uploadFile(){ var ajax = { 'url' : 'upload.php', // Скрипт обработки загрузки 'form' : 'fileUploader', // Идентификатор формы 'isUpload' : true, // Указываем на то, что будем загружать файл 'success' : uploadSuccess, // Функция обработки результата запроса 'filure' : requestFilure // Функция обработки ошибок соеденения }; Ext.Ajax.request(ajax); // Отправка запроса} // uploadFile/*** Обработка выполненого запроса* Если загрузка удалась, то выводим информацию о файле,* иначе сообщаем о неудаче* @param response XMLHttpRequest объект запроса* @param options Объект конфигурации запроса* @return void*/function uploadSuccess (response, options){ // Расшифровка ответа var result = Ext.util.JSON.decode(response.responseText); if (result.status == 'ok') { //Если все ОК, то заполняем отчет + '<td>'+result.tmpName+'</td>' + '<td>'+result.size+'</td>' + '<td>'+result.type+'</td>'; } else { //Если ошиба, то подготавливаем сообщение var report = '<td colspan="4">' + result.msg + '</td>'; } //Находим объект - тело таблицы var tbody = Ext.get('upload-report').first(); var config = { 'tag' : 'tr', 'html' : report }; //Добавляем в таблицу строку с отчетом tbody.createChild(config);} // uploadSuccess/*** Обработка ошибок соеденения* @param response XMLHttpRequest объект запроса* @param options Объект конфигурации запроса* @return void*/function requestFilure (response, options){ alert ('Загрузка не удалась');} // requestFilure// Файл js/uploader.js |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //Листинг 2-3. Динамическая загрузка файлов на сервер (php)// Файл upload.php<?php header('Content-Type: text/html'); //Устанавливаем заголовок if (isset($_FILES['newFile'])) { if ($_FILES['newFile']['error'] == 0) { //Если файл загруже, подготавливаем данные для ответа $result = array( 'status' => 'ok', 'name' => $_FILES['newFile']['name'], 'tmpName' => $_FILES['newFile']['tmp_name'], 'size' => $_FILES['newFile']['size'], 'type' => $_FILES['newFile']['type'], ); } else { //Если есть ошибки, готовим ответ с соответствующим сообщением $result = array( 'status' => 'error', 'msg' => 'Ошибка передачи файла', ); } } else { $result = array( 'status' => 'error', 'msg' => 'Файл не передан', ); } //Кодируем массив в JSON и выдаем на вывод echo json_encode($result);// upload.php |
В приведенном примере при успешной загрузке файла на сервер,клиенту приходит информация о файле.
Далее рассмотрен вариант без использования JS-библиотек. Изменения будут касаться только JavaScript’а (файл «js/uploader.js»), и удаления из файла upload.html инструкции по подключения библиотеки ExtJS. Поэтому я приведу только листинг файла «js/uploader.js»
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | //Листинг 3. Динамическая загрузка файла своими силами// Файл js/uploader.jsvar iframe;var tmpForm;/*** Функция для отправки запроса на передачу файла* @return void*/function uploadFile(){ var mainBody = document.getElementsByTagName('body')[0]; //Создаем невидимый фрейм со случайным именем iframe = document.createElement('iframe'); iframe.className = 'x-hidden'; //Размещаем фрейм в документе mainBody.appendChild(iframe); var form = document.getElementById('fileUploader'); tmpForm = form.cloneNode(true); // Создаем копию формы // Указываем в качестве цели созданый фрейм tmpForm.target = iframe.name; // Настраивайм форму-копию для передачи файла tmpForm.enctype = "multipart/form-data"; tmpForm.method = "post"; tmpForm.action = 'upload.php'; tmpForm.className = 'x-hidden'; mainBody.appendChild(tmpForm); //Указываем функцию обработки отправки запроса iframe.onload = getResponse; //Отправляем запрос tmpForm.submit(); //Удаляем форму-дубликат mainBody.removeChild(tmpForm); tmpForm.removeNode(true);} // uploadFile/*** Функция для получения ответа сервера* @return void*/function getResponse(){ //Получение содержимого тела фрейма (ответ сервера) var body = iframe.contentDocument.getElementsByTagName('body')[0]; var html = body.innerHTML; if (html) { //Эмулируем объект XMLHttpRequest var responseText = html; var response = { 'responseText' : responseText }; //Передаем полученый ответ на обработку processRequest(response); } // Удаляем созданый фрейм var mainBody = document.getElementsByTagName('body')[0]; mainBody.removeChild(iframe); iframe.removeNode(true);} // getResponse/*** Обработка выполненого запроса* Если загрузка удалась, то выводим информацию о файле,* иначе сообщаем о неудаче* @param response XMLHttpRequest объект запроса* @return void*/function processRequest (response){ eval("var result = " + response.responseText); // Расшифровка ответа if (result.status == 'ok') { //Если все ОК, то заполняем отчет + '<td>'+result.tmpName+'</td>' + '<td>'+result.size+'</td>' + '<td>'+result.type+'</td>'; } else { //Если ошиба, то подготавливаем сообщение var report = '<td colspan="4">' + result.msg + '</td>'; } //Находим объект - тело таблицы var table = document.getElementById('upload-report'); //Добавляем в таблицу строку с отчетом table.innerHTML += "<tr>" + report + "</tr>";} // processRequest// Файл js/uploader.js |
В приведенном листинге я реализовал описанный выше принцип загрузки файла на сервер по принципам технологии AJAX. Но надо отметить, что приведенный пример не имеет некоторых механизмов, которые должны повысить стабильность работы приложения. Так, например, в этом примере можно одновременно загружать только один файл, нет возможности отменить загрузку, и нет индикатора прогресса загрузки (о нем я расскажу в следующей статье).
Комментариев нет:
Отправить комментарий