суббота, 14 мая 2011 г.

Динамическая загрузка файлов на сервер с использованием AJAX

Динамическая загрузка файлов на сервер с использованием 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') { //Если все ОК, то заполняем отчет
       var report = '<td>'+result.name+'</td>'
           + '<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.js
var iframe;
var tmpForm;
 
/**
* Функция для отправки запроса на передачу файла
* @return void
*/
function uploadFile()
{
    var mainBody = document.getElementsByTagName('body')[0];
    //Создаем невидимый фрейм со случайным именем
    iframe = document.createElement('iframe');
    iframe.name = 'ajax-frame-' + Math.random(1000000);
    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') { //Если все ОК, то заполняем отчет
        var report = '<td>'+result.name+'</td>'
            + '<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. Но надо отметить, что приведенный пример не имеет некоторых механизмов, которые должны повысить стабильность работы приложения. Так, например, в этом примере можно одновременно загружать только один файл, нет возможности отменить загрузку, и нет индикатора прогресса загрузки (о нем я расскажу в следующей статье).

Комментариев нет:

Отправить комментарий