Основы AJAX: Передача данных с помощью IFRAME

Сентябрь 9, 2007 – 01:29

В данном цикле статей мы рассматриваем три базовых функциональных элемента, на которых основано большинство AJAX-приложений:

Передача данных с помощью IFRAME

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

В сердце технологии лежит функция создания скрытого фрейма:

function createIFrame() {
  var id = 'f' + Math.floor(Math.random() * 99999);
  var div = document.createElement('div');
  div.innerHTML = '<iframe style="display:none" src="about:blank"'
    +' id="'+id+'" name="'+id+'" onload="sendComplete(''
    +id+'')"></iframe>';
  document.body.appendChild(div);
  return document.getElementById(id);
}

Данная функция проверена нами в следующих браузерах: IE 7, IE 6, Firefox 2, Opera 9. С большой вероятностью она будет работать и в других менее распространенных браузерах, хотя и не во всех.

Для загрузки файла мы будем использовать HTML-форму, результат вызова которой направим в созданный IFRAME.

function sendForm(form, url, func, arg) {
  if (!document.createElement) return; // not supported
  if (typeof(form)=="string") form=document.getElementById(form);
  var frame=createIFrame();
  frame.onSendComplete = function() { func(arg, getIFrameXML(frame)); };
  form.setAttribute('target', frame.id);
  form.setAttribute('action', url);
  form.submit();
}

function sendComplete(id) {
  var iframe=document.getElementById(id);
  if (iframe.onSendComplete && typeof(iframe.onSendComplete) == 'function')
    iframe.onSendComplete();
}

function getIFrameXML(iframe) {
  var doc=iframe.contentDocument;
  if (!doc && iframe.contentWindow) doc=iframe.contentWindow.document;
  if (!doc) doc=window.frames[iframe.id].document;
  if (!doc) return null;
  if (doc.location=="about:blank") return null;
  if (doc.XMLDocument) doc=doc.XMLDocument;
  return doc;
}

Функция sendComplete будет вызвана по окончании загрузки фрейма. Её задача - обработка результата операции, либо просто уведомление пользователя о завершении загрузки. Для этого будет вызвана пользовательская программа func с двумя аргументами: пользовательский arg, плюс DOM-результат, возвращенный сервером. Мы подразумеваем, что сервер возвращает XML. Для его извлечения из фрейма служит довольно громоздкая функция getIFrameXML, нивелирующая различия между интернет-браузерами.

Приведем пример использования всего этого хозяйства:

<script type="text/javascript">
// ... сюда необходимо скопировать все вышеописанные функции  ...

var cnt=0;

function uploadComplete(element, doc) {
  if (!doc) return;
  if (typeof(element)=="string") element=document.getElementById(element);
  element.innerHTML='Результат запроса #'+(++cnt)
    +': '+doc.documentElement.firstChild.nodeValue;
}
</script>

<form id="ajaxUploadForm" method="post" enctype="multipart/form-data"
onsubmit="sendForm(this,'uploadFile.php',uploadComplete,'resultDiv');return true;">
<label>Файл: <input type="file" name="uploadFile" /></label>
<input type="submit" value="Загрузить" />
</form>
<input type="button" value="Альтернативный вызов загрузки файла"
onclick="sendForm('ajaxUploadForm','uploadFile.php',uploadComplete,'resultDiv')" />
<div id="resultDiv"></div>

Для справки приведем текст демонстрационного файла uploadFile.php:

<?php header("Content-type: application/xml; charset=UTF-8");
echo '<?xml version="1.0" encoding="UTF-8" ?>' ?>
<result>Получен файл [<?php echo($_FILES['uploadFile']['name']); ?>]
 размером <?php echo($_FILES['uploadFile']['size']); ?> байт</result>

Ссылка на работающий пример: sample_ajax_iframe_upload.htm

  1. Комментарии (43) к статье “Основы AJAX: Передача данных с помощью IFRAME”

  2. Июль 25, 2008. От dilmo.

    Хороший пример, проверил sample - работает, вот только интересно, что я копирую код, запускаю у себя и у меня в FF вылетает ошибка
    Ошибка: uncaught exception: 0?@5I5=> ?>;CG0BL A2>9AB2>: XMLDocument.location

    а когда smaple запускаешь все работает… я ничего понять не могу, может кто объяснит ?

  3. Июль 25, 2008. От admin.

    Файл uploadFile.php Вы тоже скопировали на свой сервер? Он должен быть в той же директории, что и sample_ajax_iframe_upload.htm

    Чтобы использовать uploadFile.php с нашего сервера нужно изменить код вызова sendForm(…,’http://web-tec.info/samples/uploadFile.php’,…)

    Подобная ошибка может также означать, что Вы открыли файл из локальной директории на Вашем ПК. Это может не работать, так как новые браузеры относят локальные папки к особой зоне безопасности и не разрешают многое. Проверять надо на http-сервере.

    Проверьте также в других браузерах. Если в них тоже не работает, значит что-то не так перенесли.

  4. Июль 26, 2008. От dilmo.

    Спасибо за комментарий, все работает теперь :)

  5. Июль 28, 2008. От dilmo.

    Еще вопросик, а как теперь получить доступ к содержимому IFrame
    Т.е. я загрузил некий XML с сервера, теперь мне надо получить доступ с этому XML
    желательно если он будет одной строкой, т.е. я смогу использовать регулярные выражения для парсинга

  6. Июль 28, 2008. От admin.

    Ответ на Ваш вопрос есть в тексте статьи. Попробую изложить иначе:

    При вызове функции sendForm() Вы указываете функцию (параметр func), которая будет вызвана по окончании загрузки фрейма. Эта функция будет вызвана с двумя параметрами: arg и doc. arg - это Ваша переменная, которую Вы передаете в функцию sendForm(), а doc - разобранный XML-документ, загруженный во фрейм. Вот из этой переменной doc Вам и следует извлекать данные.

    Все это проиллюстрировано в примере sample_ajax_iframe_upload.htm - см. функцию uploadComplete(). Она извлекает из XML-документа эелемент ‘result’ следующим образом: doc.documentElement.firstChild.nodeValue

  7. Июль 28, 2008. От dilmo.

    можно ли каким либо образом получить весь XML в виде строки ? без написания рекурсивных процедур обхода всех child’ов

  8. Июль 28, 2008. От admin.

    В принципе можно, но зависит от браузеров. Вот функция превращения DOM в строку:

    function serializeXML(doc) {
    if (typeof(doc.xml)!=”undefined”) { //userAgent.isInternetExplorer
    return doc.xml;
    }
    else {
    try { // if (userAgent.isMozilla || userAgent.isOpera)
    var s=new XMLSerializer();
    return s.serializeToString(doc);
    } catch(e) {
    return doc.xml;
    }
    }
    }

    Вообще на практике чаще оказывается проще извлечь информацию из DOM.

  9. Дек 21, 2008. От Razrabotchik.

    Помогите, пожалуйста! Я никаким образом не могу извлечь данные, если делаю xml хотя бы на один уровень сложнее. Тут текстТут текст
    Как только я ни упражнялся, ничего кроме первого узла получить не удаётся, выдаёт ошибку, что узла нет. Почему так? С уважением. Евгений.

  10. Дек 22, 2008. От agreen.

    Помогите понять, что твориться.

    В пхп файле переписал:
    move_uploaded_file($_FILES[’uploadFile’][’tmp_name’], $_SERVER[’DOCUMENT_ROOT’].”/data/images/”.$_POST[”directory”].”/”.rand().”.jpg”);

    echo “Ok!”;
    В HTML в форму добавил:

    Файл !загружается!, но ответ от ПХП приходит следующий:
    Notice: Undefined index: uploadFile in D:\www\ceramir\modules\specific\interface\controls

    \image_upload\handler.php on line 13

    Notice: Undefined index: directory in D:\www\ceramir\modules\specific\interface\controls

    \image_upload\handler.php on line 13

  11. Дек 24, 2008. От admin.

    @Razrabotchik

    В приведенном примере в функции uploadComplete() выводится именно первый элемент полученного XML-документа (к нему идет явное обращение: doc.documentElement.firstChild.nodeValue). Это сделано в качестве иллюстрации, чтобы не усложнять код.

    Для решения реальных задач используйте стандартные методы доступа к XML-DOM. Например, второй элемент можно получить так: doc.documentElement.firstChild.nextSibling.nodeValue и т.п.

  12. Дек 24, 2008. От admin.

    @agreen

    А у вас в HTML-форме есть параметр “directory”?

  13. Мар 3, 2009. От DirtyAss.

    Как к этому делу прикрутить loading.gif на время загрузки файла

  14. Мар 3, 2009. От admin.

    @DirtyAss

    В функции sendForm() перед строкой form.submit() вставьте отображение картинки “loading”, а в функции sendComplete() уберите её.

  15. Июнь 30, 2009. От host.

    Скажите пожалуйста, можно ли получить результат не в виде XML, а в виде обычно текста ? Спасибо

  16. Июль 1, 2009. От admin.

    @host

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

  17. Ноя 9, 2009. От sat-lin.

    Вроде бы все хорошо но не как не могу привязать копирование файла в директрорию, вы не могли бы дать какой нибудь совет или пример? Заранее спасибо!

  18. Ноя 9, 2009. От admin.

    Простите, не понял, что значит “привязать копирование файла в директорию”. Какую директорию? Зачем копирование? К чему привязать?

  19. Ноя 17, 2009. От resort.

    Подскажите можно ли получить результат в файле uploadFile.php в виде обычного HTML а не в XML. Как это сделать, целый день промучился никак не могу разобраться.

  20. Ноя 17, 2009. От admin.

    Если файл uploadFile.php выдаст результат в виде обычного HTML, с соответствующим Content-type: text/html, то в браузере этот результат все равно будет доступен только в виде DOM (HTML-DOM).

    Вообще, XML дает универсальное решение. Стоит ли мудрить? Напишите, почему возникла такая задача?

  21. Ноя 17, 2009. От resort.

    Совсем запутался, не могу разобраться. XML я не знаю, хотя понимаю что нужно разобраться с ним, думал весь результат выводить одним махом без сложной системы разметки, но теперь сомневаюсь что мои мысли направленны правильно. Допустим, я хочу загружать не один файл, а несколько, соответственно каждый результат должен быть отделенным от других т.е. мне нужно сделать разметку и получить результаты как и писалось выше doc.documentElement.firstChild.nextSibling.nodeValue , но тут засада, не могу понять как нужно сформировать XML документ что бы это работало, пока получаю ответ null

  22. Ноя 17, 2009. От admin.

    Если XML-код выглядит так:

    <results><result>OK</result><result>Error</result><result>Crash!</result></results>

    то в функции uploadComplete обрабатываем его так:

    var results=doc.documentElement.childNodes;
    var codes=[];
    for (var i=0; i<results.length; i++) {
    codes[i]=results[i].firstChild.nodeValue;
    }

    В результате имеем:

    codes[0] = OK
    codes[1] = Error
    codes[2] = Crash!

    (не проверял, но должно работать)

  23. Ноя 17, 2009. От resort.

    Может не достаточно ясно излогаю свои мысли, может будет понятнее если я скажу что я хочу вообще =).
    В общем понятно что хочется организовать загрузку изображений. При этом задача стоит что бы избежать загрузку изображений при обновлении страницы или возврате на страницу (по истории страниц).
    В файле uploadFile.php планируется внедрить кусок кода который сжимает и ресайзит изображения в зависимости от их размера идет разное качество сжатия, подпись изображения ну и копирования вновь созданного файла в свою дерикторию, это все есть и это хочу добавить в этот файл.
    Но появилась мысль загружать не одно а несколько изображений, при чем количество загружаемых изображений будет изменяться в конфиг файле, где-то может понадобиться загружать два изображения, а где-то пять. Отсюда и вся свистопляска, не хочется постоянно лазить в код и менять структуру, посему хочется сделать динамическое формирование выдачи ко-во изображений.
    Так же будет проверка на размер изображений и ограничений по размеру изображения и формата изображений. Допустим из 5 изображений два не подходят по неким параметрам (размер, формат), в результате нужно вывести результат выполненных работ и показать пользователю какие изображения загружены, а какие нет. Именно по этому мне нужно выводить каждый результат отдельно.
    Это подробный и развернутый вопрос к моему вопросу выше. =))

  24. Ноя 17, 2009. От resort.

    спасибо, сейчас буду мудрить. Мне главное скелет сварганить, все остальное прикручу потом.

  25. Ноя 17, 2009. От resort.

    Никак не получается, уже все перепробовал, не хочет:
    function uploadComplete(element, doc) {
    if (!doc) return;
    if (typeof(element)==”string”) element=document.getElementById(element);
    var results=doc.documentElement.childNodes;
    var codes=[];
    for (var i=0; i(знак меньше)results.length; i++) {
    codes[i]=results[i].firstChild.nodeValue;
    }

    element.innerHTML=’Рузультат:’+codes[0]+codes[1]+codes[2];
    }

    Где что не так?!

  26. Ноя 17, 2009. От admin.

    В чем выражается “не хочет”?

    Добавьте отладочные вызовы alert() в код, чтобы понять, в каком месте происходит сбой.

  27. Ноя 17, 2009. От resort.

    Ошибку нашел, знать о ней не знал, догадался. В общем в XML файле все написал в строку, и все заработало, но так очень неудобно т.к. читаемость кода никакая, очень неудобно. Есть возможность писать все не в одну строку?

  28. Ноя 17, 2009. От admin.

    Ясно. Можно.

    Дело в том, что в XML любой текст (даже если это пробел) между тегами является child-ом. А приведенный мной выше фрагмент кода - он действует вслепую - для упрощения. Он не рассчитывает, что у корневого элемента будут другие child-ы, кроме ‘result’.

    Полный более корректный код будет такой:

    var results=doc.documentElement.childNodes;
    var codes=[];
    var count=0;
    for (var i=0; i<results.length; i++) {
    if (results[i].nodeName==”result”) { // отбираем только элементы result
    codes[count++]=results[i].firstChild.nodeValue;
    }
    }

  29. Ноя 17, 2009. От resort.

    Огромное спасибо! Все отлично заработало, немного переделал под неограниченное количество загружаемых изображений за один подход, все как часы работает.
    Еще раз спасибо!!!

  30. Ноя 17, 2009. От resort.

    И еще один момент, забыл про него. Как использовать стили в XML Допустим такой вид Получен файл бла бла бла байт Загружен успешно и рядом показываем само изображение

    В результате не зеленая надпись не сама картинка не отображаются, и как я догадываюсь попросту не передаются из XML файла. Как это можно сделать?

  31. Ноя 17, 2009. От resort.

    Блин, скобки отсеялись (((
    result
    между результ и /результ можно ли вставить картинку и покрасить текст в какой-нибудь цвет?!
    /result

  32. Ноя 17, 2009. От admin.

    Стили и оформление - это задача принимающего скрипта. XML передает только данные. Например, в случае OK он может содержать дополнительный атрибут image:

    <results><result image=”http://domain.ru/img/878782.jpg”>OK</result><result>Error</result><result>Crash!</result></results>

    Тогда принимающий скрипт может делать так:

    var results=doc.documentElement.childNodes;
    var codes=[], images[];
    var count=0;
    for (var i=0; i<results.length; i++) {
    if (results[i].nodeName==”result”) { // отбираем только элементы result
    images[count]=results[i].getAttribute(”image”);
    codes[count++]=results[i].firstChild.nodeValue;
    }
    }

    element.innerHTML=”<img src=’”+images[0]+”‘ />”;

  33. Ноя 17, 2009. От resort.

    Все, теперь все предельно ясно. Как плохо когда в чем-то дерево =). Большое спасибо за помощь, очень помогли!
    В ближайшее время сажусь учить XML, полезная штука как я понял.

  34. Дек 31, 2009. От borisovks.

    Здравствуйте, я решил этот метод реализовать на своем сайте, для добавления картинок, и столкнулся со следующей проблемой: При передачи таким методом картинок, происходит неправильное перекодирование изображений, в резултате чего картини получаются битыми, хотя с текстовыми файлами все нормально при передаче. В самом скрипте я еще ничего толком не менял, добавил только в uploadFile.php сохранение картинки… У меня такое ощущение что это дело в сервере, он почему-то перекодирует изображения… Подскажите куда копать?

  35. Дек 31, 2009. От admin.

    borisovks,

    Метод в принципе рабочий. Надо искать причину, почему у вас не получается.

    Загрузите картинку сюда на тестовую страницу: http://web-tec.info/samples/sample_ajax_iframe_upload.htm

    Посмотрите число полученных байт. Соответствует ли оно исходному числу байт в вашей картинке? Сравните с размером файла, который сохраняет ваш скрипт на вашем сервере.

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

    Скопируйте сюда текст своего серверного скрипта.

    Пока больше идей нет.

  36. Дек 31, 2009. От borisovks.

    Спасибо вам за помощь, проблему я решил!

    Оказывается меня коснулось “проклятье Русского апача”. В том то и дело файлы по размерам совпадали, долго не мог понять в чем подвох, оказалось дело в хостере и его злобном mod_charset

    Зметил это не сразу, поскольу имею привычку всегда прописывать кодировку…

    В общем после бессонной ночи гуглений решеиние было найдено через .htaccess добавлением туда
    AddDefaultCharset Off

    CharsetDisable On
    CharsetRecodeMultipartForms Off

    З.Ы. урок мне теперь на всю жизнь! :-)

  37. Янв 4, 2010. От borisovks.

    Извините, но возникла еще одна проблемка… Без изменений скрипт отлично работает, но я решил доработать его, что-бы можно было получать несколько чайлдов, тоесть то же что добивался resort

    Но возникает ошибка при отравке файла:
    Method Not Allowed

    The requested method POST is not allowed for the URL /up_ajax/sample.htm.

    Хотя метод пост обязан работать, поскольку у меня еще много форм работают без ошибок, и все передаеют, стоило добавить …… и соответствующий код для принимающего скрипта, как полезло это… Если есть мысли, подскажите пожалуйста.

  38. Янв 4, 2010. От admin.

    Вероятно POST должен быть направлен не на /up_ajax/sample.htm, а на php-скрипт. Проверьте параметр url, который передается в функцию sendForm.

  39. Янв 4, 2010. От borisovks.

    Нет, там все нормально, как в примере. Тоесть добавился только блок кода для uploadComplete ну и для php скрипта, если верну обратно - то передает…

  40. Янв 4, 2010. От admin.

    А откуда тогда взялась ссылка “/up_ajax/sample.htm”? Если я правильно понимаю, ваш веб-сервер ловит POST-запрос на эту ссылку и ругается в ответ, что htm-страницы не могут обрабатывать POST-запросы. Лично я с таким поведением не сталкивался, но вполне могу допустить его существование. В принципе логично.

  41. Янв 4, 2010. От borisovks.

    Спасибо за ответ. Но вообще-то это была реакция на действия html страницы, которая располагается по такому адресу “/up_ajax/sample.htm”. Да и не может у меня взяться таких ссылок, поскольку файл обработчик лежит там же где и html форма. Ну и в url у меня точно прописан адрес обработчика…
    Может дело таки в хостере

  42. Июль 16, 2010. От Ivan.

    @admin

    «Чтобы использовать uploadFile.php с нашего сервера нужно изменить код вызова sendForm(…,’http://web-tec.info/samples/uploadFile.php’,…)»

    В настоящем виде способ не кросс-доменный, так не получится.

  1. 2 Trackback(s)

  2. Сен 9, 2007: WEB-TEC.info » Blog Archive » Основы AJAX: Передача данных с помощью XMLHttpRequest
  3. Сен 9, 2007: WEB-TEC.info » Blog Archive » Основы AJAX: Передача данных с помощью элементов <script> (+JSONP)

Необходимо войти, чтобы оставить комментарий.