]]>
ваш баннер
]]>

Как реализовать асинхронную загрузку файлов с помощью JavaScript и PHP

9 марта, 2008

Логотип к статье Асинхронная загрузка файлов

В комментариях к одной из предыдущих статей меня попросили показать пример загрузки файлов на север с помощью технологии AJAX и фреймворка CodeIgniter.

Тогда я ответил, что это несложно и пообещал показать пример. Но, я поторопился и забыл упомянуть о паре «нюансов», связанных с этой операцией :-) . Так что, исправляю ошибку.

Механизм отправки

Прежде всего, нужно четко понимать, что отправить (загрузить) на сервер файл с помощью AJAX нельзя.

Тем не менее, можно организовать процесс загрузки так, что с точки зрения посетителя загрузка будет выглядеть асинхронной. Т.е. посетитель укажет имя файла и нажмет кнопку «Загрузить». После этого, увидит умную надпись вроде «Подождите, идет загрузка…» или какую-нибудь анимацию. А после окончания загрузки – сообщение с результатами. Страница, которую он видит, перезагружена не будет.

Но при этом отправка файла будет выполнена обычным способом.

Идея заключается в использовании невидимого фрейма (iframe), атрибута формы target и JavaScript.

Работает это так. Создаем страницу с формой и скрытым фреймом (index.html).

Code (html4strict)
  1. <form action="http://www.mysite.com/upload.php" method="post" target="hiddenframe" enctype="multipart/form-data">
  2. <input type="file" id="userfile" name="userfile" />
  3. <input type="submit" value="Загрузить" />
  4. </form>
  5. <div id="res"></div>
  6. <iframe id="hiddenframe" name="hiddenframe" style="width:0px; height:0px; border:0px"></iframe>

Обратите внимание, что в атрибуте target формы указан id фрейма.

На этом этапе если нажать кнопку «Загрузить» файл будет отправлен серверу, а результат – загружен в iframe, т.е. с точки зрения посетителя никаких изменений не произойдет, т.к. фрейм невидимый.

Примечание
. Как вы знаете, в iframe может быть вставлена любая html страница или результат запроса.

Теперь нужно передать данные из фрейма на основную страницу.

Мы заранее не знаем, когда придет ответ сервера, поэтому логично, что он и должен обновлять основную страницу.

Для этого в ответ сервера необходимо вставить JavaScript код, который будет выполнен после окончания загрузки.

Обычно этот код просто вызывает js-функцию, объявленную в основной странице и передает ей результаты выполнения запроса.

Схематически асинхронная отправка файла изображена на диаграмме.

Процесс загрузки файла и обновления страницы

Примечание. Описанная техника называется Remote Scripting. Подробнее о ней можно почитать в этой статье.

Теперь посмотрим, как это работает на практике.

Как я и обещал, в примере будет использоваться фреймворк CodeIgniter.

Прежде всего, создаем контроллер (application/controllers/asyncuploader.php), с двумя методами.

Code (php)
  1. class AsyncUploader extends Controller {
  2.     function AsyncUploader() {
  3.         parent::Controller();
  4.         $this->load->helper(‘url’);
  5.         $this->load->helper(‘form’);
  6.     }
  7.     function index() {
  8.         $pageData[‘title’] = "Асинхронная загрузка файлов";
  9.         $this->load->view(‘main’, $pageData);
  10.     }
  11.     function do_upload() {
  12.     //…
  13.     }
  14. }

index() – отображает основную страницу с формой;
do_upload() – получает запрос с файлами и возвращает результат (ниже мы рассмотрим его подробно).

После этого создаем представление (application/views/main.php).

Code (php)
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN">
  2. <html xmlns="http://www.w3.org/1999/xhtml" lang="ru">
  3. <head>
  4. <title><?php echo $title; ?></title>
  5. <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  6. <script src="<?php echo base_url()."scripts.js" ?>" type="text/javascript"></script>
  7. </head>
  8. <body>
  9. <?php
  10. $attr = array(‘target’ => ‘hiddenframe’, ‘onsubmit’ => ‘hideBtn()’);
  11. echo form_open_multipart(‘asyncuploader/do_upload’, $attr);
  12. ?>
  13. <p>
  14. <input type="file" id="userfile" name="userfile" />
  15. </p>
  16. <div id="sendBtn">
  17. <input type="submit" value="Загрузить" />
  18. </div>
  19. <?php
  20. echo form_close();
  21. ?>
  22. <div id="res"></div>
  23. <iframe id="hiddenframe" name="hiddenframe" style="width:0px; height:0px; border:0px"></iframe>
  24. </body>
  25. </html>

Тут все достаточно просто. Мы создали форму (строки 10-20) с полем file и кнопкой «Загрузить».

После формы расположены пустой блок «res», в который будет вставлен ответ сервера и невидимый iframe (строка 23).

При отправке данных формы будет вызвана функция hideBtn(), размещенная в файле scripts.js. Этот файл загружается в заголовке страницы (строка 6).

Рассмотрим его подробнее.

Code (javascript)
  1. function hideBtn() {
  2.     document.getElementById("sendBtn").innerHTML = "Подождите, идет загрузка…";
  3. }
  4.  
  5. function handleResponse(mes) {
  6.     var resElement = document.getElementById("res");
  7.     document.getElementById("sendBtn").innerHTML =
  8.         "<input type=\"submit\" value=\"Загрузить\" />";
  9.     if (mes.error != null) {
  10.         resElement.innerHTML = "Возникли ошибки во время загрузки файла: " + mes.error;
  11.     }
  12.     else {
  13.         resElement.innerHTML = "Файл " + mes.file_name + " загружен";
  14.     }
  15. }

Здесь объявлены две функции:
hideBtn() – прячет кнопку «Загрузить» и показывает вместо нее сообщение;
handleResponse() – эта функция отображает результаты загрузки на странице. Она вызывается из iframe. А в качестве параметра получает объект с сообщениями сервера.

Теперь переходим к методу do_upload().

Он принимает данные формы, проверяет и сохраняет файл и возвращает результат.

Code (php)
  1. function do_upload() {
  2.     $config[‘upload_path’] = ‘./uploads/’;
  3.     $config[‘allowed_types’] = ‘zip|rar’;
  4.     $config[‘max_size’] = ‘500′;
  5.  
  6.     $this->load->library(‘upload’, $config);
  7.  
  8.     if ( ! $this->upload->do_upload())
  9.     {
  10.         $mes = array(‘error’ => $this->upload->display_errors());
  11.     }
  12.     else
  13.     {
  14.         $mes = $this->upload->data();
  15.     }
  16.     //создаем js массив
  17.     $res = "<script type=\"text/javascript\">";
  18.     $res .= "var data = new Object;";
  19.     foreach ($mes as $key => $item) {
  20.         $res .= "data.".$key." = \"".$item."\";";
  21.     }
  22.     $res .= "window.parent.handleResponse(data);";
  23.     $res .= "</script>";
  24.     echo $res;
  25. }

Для загрузки файла используется библиотека upload, входящая в состав CodeIgniter.

Пользоваться ей несложно. Достаточно создать массив с настройками и загрузить библиотеку (строки 2-6). В настройках можно задать размещение загружаемых файлов, их допустимый тип, размер и т.п.

Примечание. Подробнее почитать о работе с библиотекой можно в официальном руководстве в разделе File Uploading Class.

После того, как настройки заданы, вызываем метод do_upload() (строка 8).

Дальше начинается более интересная часть. Мы должны сформировать ответ сервера с JavaScript кодом, который вызовет функцию handleResponse() основной страницы и передаст ей результаты загрузки.

Прежде всего, обратите внимание, что в зависимости от результатов выполнения у нас будет массив, содержащий либо описание ошибки, либо данные загруженного файла (строка 14).

Теперь посмотрите на строки 17-23. Мы создаем текстовую строку, содержащую обычный JavaScript код. Когда ответ сервера будет загружет во фрейм, код будет автоматически выполнен.

Разберем его подробнее.

Нам нужно передать результаты обработки, которые находятся в массиве $mes (php). Т.к. количество полей может меняться, то удобно использовать объект JavaScript, названия свойств которого будут совпадать с ключами массива.

Для этого мы используем цикл (php), каждая итерация которого создает строку вида:

Code (javascript)
  1. data.имя_ключа = "значение";

После этого, мы вызываем функцию handleResponse (строка 22), которой передаем сформированный js-объект.

Таким образом, функция handleResponse в параметре получит объект со всеми необходимыми данными, доступ которым можно получить через его свойства, что мы и делаем (листинг функции приведен ранее).

Как видите, используя несложные обходные пути можно обойти ограничения AJAX и загружать файлы асинхронно (ну, во всяком случае, с точки зрения посетителя) :-) .

До встречи!

Понравилась статья? Подпишитесь на продолжение rss link !

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в CodeIgniter, HTML, JavaScript, PHP

]]>

Комментарии (21)

Вы можете отслеживать обсуждение записи с помощью RSS 2.0 rss link

Вы также можете оставить комментарий, или трекбек с Вашего сайта.

  1. Алексей 09.03.2008 в 13:17 (Ответить)

    Спасибо! Интересный материал. А главное, я заметил, что вы выкладываете обзоры именно того и тогда, в чем есть первостепенная потребность ))

    1. Владимир 09.03.2008 в 19:22 (Ответить)

      Стараемся :-)

  2. Tazman 09.03.2008 в 16:55 (Ответить)

    Спасибо за статью! Очень помогло. Интересный метод. Надо только добавить немного эффектов и prototype/mootools для красоты :) P.S. Вообще есть еще вариант с использованием флэша.

    1. Владимир 09.03.2008 в 19:27 (Ответить)

      Флеш требует flash player, а это - минус. Правда, небольшой, т.к. он практически у всех установлен.

      Кроме того, можно использовать Java applet и компоненты ActiveX, но первый вариант требует JRE (java runtime enviroment), который меньше распространен чем тотже флеш, а второй - вообще должен быть отключен в браузере с точки зрения безопасности.

    2. Алексей 10.03.2008 в 00:28 (Ответить)

      а почему не script.aculo.us? :)

      1. Sergey 10.03.2008 в 16:15 (Ответить)

        Тогда уже jquery :)

  3. Sergey 09.03.2008 в 19:49 (Ответить)

    Спасибо за статью.
    Весьма наглядно и понятно.

  4. ACID Jesus 10.03.2008 в 08:34 (Ответить)

    На самом деле флеш далеко не везде установлен, так что вариант с ифрейм определённо решает - санкс за статью 8-)

    1. Sergey 10.03.2008 в 16:19 (Ответить)

      Действительно, может быть дома у большинства флеш и установлен, но на работе в больших компаниях по политике безопасности его ставят крайне редко и неохотно.
      На вопрос “почему” не отвечу. Сам не знаю. Но факт остается фактом.

  5. Иной 09.04.2008 в 04:45 (Ответить)

    Или я что то не понял или не так сделал, в каких файлах, в каких директориях что должно быть помещено(какие имена и расширения)?

    1. Владимир 09.04.2008 в 16:09 (Ответить)

      Загрузку файлов выполняет функция do_upload() (в предпоследнем листинге).
      Размещение и типы файлов указываются в массиве $config (‘upload_path’ и ‘allowed_types’).
      Причем, размещение должно быть указано относительно корня сайта (т.е. папки в которой находится index.php, если вы используете CodeIgniter).
      В данном случае это папка uploads. Естественно, нужно установить права на запись в эту папку.

      Типы перечисляются через символ “|”. Загружаться будут только файлы тех типов, которые указаны в списке.

  6. SergiusD 14.04.2008 в 10:39 (Ответить)

    А как быть если надо аплоадить файл на другой сервак, то есть хост другой. Есть какой нибудь рецепт этого?

    1. SergiusD 14.04.2008 в 11:12 (Ответить)

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

      OPERA: message: Security error: attempted to read protected variable
      FF: Ошибка: uncaught exception: Permission denied to get property HTMLDocument.getElementById
      IE: Отказано в доступе

      если сделать то же самое из родителького окна, то есть по тамауту запрашивать содержимое, пока оно не загрузится, то тот же самый результат

      1. Владимир 14.04.2008 в 21:48 (Ответить)

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

        Варианты решения:
        1) загружать файл на сервер, с которого был загружен скрипт, а с него отправлять на другой сервер.
        2) отправить браузеру редирект на сервер, который должен принимать файл.

        Может есть и другие варианты.

        1. Алексей 14.04.2008 в 23:26 (Ответить)

          вот так обходят запреты
          назыается JSON with Padding (JSONP)

        2. Алексей 14.04.2008 в 23:27 (Ответить)

          исочник

          1. SergiusD 15.04.2008 в 07:49 (Ответить) (Достигнут максимальный уровень вложенности комментариев)

            Честно говоря не понял… старался :)
            есть x
            есть

            и как заставить чтобы из iframe изменилось содержимое dest? Какой нить пример если можно

          2. SergiusD 15.04.2008 в 07:51 (Ответить) (Достигнут максимальный уровень вложенности комментариев)

            Честно говоря не понял… старался :)
            есть [div id=”dest”]x[/div]
            есть
            [iframe id=”otherserver” src=”otherserver”]
            [script type=”text/javascript” src=”http://travel.com/findItinerary?username=a&reservationNum=b&output=json&callback=showItinerary”/]
            [/iframe]
            и как заставить чтобы из iframe изменилось содержимое dest? Какой нить пример если можно

          3. Владимир 15.04.2008 в 11:39 (Ответить)

            Я хочу уточнить. JSON with Padding предполагает отправку (получение) данных с помощью JSON.
            А проблема как раз в том, что с помощью JSON файл отправить нельзя.

            Отправка осуществляется из обычной формы (form/multipart) и ответ приходит обычный, только попадает он не на основную страницу, а в iframe.
            JavaScript тут используется только для передачи ответа сервера из невидимого iframe в основную страницу.

            А вот вариант ajax-прокси (наверное, в данном случае это будет просто прокси), должен работать.
            В простейшем варианте нужно в серверном скрипте не сохранять файл, а отправить его на другой сервер.

            Правда вариант не очень хороший, возникнет перерасход трафика, да и нагрузка на основной сервер возрастет…

            В общем, вопрос такой: “Почему вы не можете страницу с формой загрузки разместить на том же сервере, на который будете закачивать файлы?”

            P.S. Алексею спасибо за ссылку на статью. Очень интересная!

          4. SergiusD 16.04.2008 в 07:10 (Ответить) (Достигнут максимальный уровень вложенности комментариев)

            Да, статья интересная :)
            Просто не хочется разносить шаблоны по доменам… причем штука которая аплоадит это плагин к tinyMCE. В итоге отказался от ajax в данной форме и просто в action указал другой домен, после чего тот сервер редиректит обратно.
            Конечно с ajax получилосьбы стройнее, но увы.

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

Введите ваш комментарий

* - обязательные для заполнения поля

Quicktags:

]]>