Основы AJAX: Передача данных с помощью IFRAME
Сентябрь 9, 2007 – 01:29В данном цикле статей мы рассматриваем три базовых функциональных элемента, на которых основано большинство AJAX-приложений:
- передача данных с помощью XMLHttpRequest
- передача данных с помощью IFRAME
- передача данных с помощью элементов <script>
Передача данных с помощью 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

Комментарии (43) к статье “Основы AJAX: Передача данных с помощью IFRAME”
Хороший пример, проверил sample - работает, вот только интересно, что я копирую код, запускаю у себя и у меня в FF вылетает ошибка
Ошибка: uncaught exception: 0?@5I5=> ?>;CG0BL A2>9AB2>: XMLDocument.location
а когда smaple запускаешь все работает… я ничего понять не могу, может кто объяснит ?
Файл uploadFile.php Вы тоже скопировали на свой сервер? Он должен быть в той же директории, что и sample_ajax_iframe_upload.htm
Чтобы использовать uploadFile.php с нашего сервера нужно изменить код вызова sendForm(…,’http://web-tec.info/samples/uploadFile.php’,…)
Подобная ошибка может также означать, что Вы открыли файл из локальной директории на Вашем ПК. Это может не работать, так как новые браузеры относят локальные папки к особой зоне безопасности и не разрешают многое. Проверять надо на http-сервере.
Проверьте также в других браузерах. Если в них тоже не работает, значит что-то не так перенесли.
Спасибо за комментарий, все работает теперь :)
Еще вопросик, а как теперь получить доступ к содержимому IFrame
Т.е. я загрузил некий XML с сервера, теперь мне надо получить доступ с этому XML
желательно если он будет одной строкой, т.е. я смогу использовать регулярные выражения для парсинга
Ответ на Ваш вопрос есть в тексте статьи. Попробую изложить иначе:
При вызове функции sendForm() Вы указываете функцию (параметр func), которая будет вызвана по окончании загрузки фрейма. Эта функция будет вызвана с двумя параметрами: arg и doc. arg - это Ваша переменная, которую Вы передаете в функцию sendForm(), а doc - разобранный XML-документ, загруженный во фрейм. Вот из этой переменной doc Вам и следует извлекать данные.
Все это проиллюстрировано в примере sample_ajax_iframe_upload.htm - см. функцию uploadComplete(). Она извлекает из XML-документа эелемент ‘result’ следующим образом: doc.documentElement.firstChild.nodeValue
можно ли каким либо образом получить весь XML в виде строки ? без написания рекурсивных процедур обхода всех child’ов
В принципе можно, но зависит от браузеров. Вот функция превращения 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.
Помогите, пожалуйста! Я никаким образом не могу извлечь данные, если делаю xml хотя бы на один уровень сложнее. Тут текстТут текст
Как только я ни упражнялся, ничего кроме первого узла получить не удаётся, выдаёт ошибку, что узла нет. Почему так? С уважением. Евгений.
Помогите понять, что твориться.
В пхп файле переписал:
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
@Razrabotchik
В приведенном примере в функции uploadComplete() выводится именно первый элемент полученного XML-документа (к нему идет явное обращение: doc.documentElement.firstChild.nodeValue). Это сделано в качестве иллюстрации, чтобы не усложнять код.
Для решения реальных задач используйте стандартные методы доступа к XML-DOM. Например, второй элемент можно получить так: doc.documentElement.firstChild.nextSibling.nodeValue и т.п.
@agreen
А у вас в HTML-форме есть параметр “directory”?
Как к этому делу прикрутить loading.gif на время загрузки файла
@DirtyAss
В функции sendForm() перед строкой form.submit() вставьте отображение картинки “loading”, а в функции sendComplete() уберите её.
Скажите пожалуйста, можно ли получить результат не в виде XML, а в виде обычно текста ? Спасибо
@host
Поэкспериментируйте. В любом случае текст нужно извлекать из DOM-модели i-фрейма. Не забывайте, что даже если i-фрейм получает ответ сервера в виде простого текста, он все равно пытается построить из него HTML-DOM для отображения содержимого (даже если вы и сделали его невидимым). К исходному тексту, полученному от сервера, мы в данном случае доступа не имеем.
Вроде бы все хорошо но не как не могу привязать копирование файла в директрорию, вы не могли бы дать какой нибудь совет или пример? Заранее спасибо!
Простите, не понял, что значит “привязать копирование файла в директорию”. Какую директорию? Зачем копирование? К чему привязать?
Подскажите можно ли получить результат в файле uploadFile.php в виде обычного HTML а не в XML. Как это сделать, целый день промучился никак не могу разобраться.
Если файл uploadFile.php выдаст результат в виде обычного HTML, с соответствующим Content-type: text/html, то в браузере этот результат все равно будет доступен только в виде DOM (HTML-DOM).
Вообще, XML дает универсальное решение. Стоит ли мудрить? Напишите, почему возникла такая задача?
Совсем запутался, не могу разобраться. XML я не знаю, хотя понимаю что нужно разобраться с ним, думал весь результат выводить одним махом без сложной системы разметки, но теперь сомневаюсь что мои мысли направленны правильно. Допустим, я хочу загружать не один файл, а несколько, соответственно каждый результат должен быть отделенным от других т.е. мне нужно сделать разметку и получить результаты как и писалось выше doc.documentElement.firstChild.nextSibling.nodeValue , но тут засада, не могу понять как нужно сформировать XML документ что бы это работало, пока получаю ответ null
Если 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!
(не проверял, но должно работать)
Может не достаточно ясно излогаю свои мысли, может будет понятнее если я скажу что я хочу вообще =).
В общем понятно что хочется организовать загрузку изображений. При этом задача стоит что бы избежать загрузку изображений при обновлении страницы или возврате на страницу (по истории страниц).
В файле uploadFile.php планируется внедрить кусок кода который сжимает и ресайзит изображения в зависимости от их размера идет разное качество сжатия, подпись изображения ну и копирования вновь созданного файла в свою дерикторию, это все есть и это хочу добавить в этот файл.
Но появилась мысль загружать не одно а несколько изображений, при чем количество загружаемых изображений будет изменяться в конфиг файле, где-то может понадобиться загружать два изображения, а где-то пять. Отсюда и вся свистопляска, не хочется постоянно лазить в код и менять структуру, посему хочется сделать динамическое формирование выдачи ко-во изображений.
Так же будет проверка на размер изображений и ограничений по размеру изображения и формата изображений. Допустим из 5 изображений два не подходят по неким параметрам (размер, формат), в результате нужно вывести результат выполненных работ и показать пользователю какие изображения загружены, а какие нет. Именно по этому мне нужно выводить каждый результат отдельно.
Это подробный и развернутый вопрос к моему вопросу выше. =))
спасибо, сейчас буду мудрить. Мне главное скелет сварганить, все остальное прикручу потом.
Никак не получается, уже все перепробовал, не хочет:
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];
}
Где что не так?!
В чем выражается “не хочет”?
Добавьте отладочные вызовы alert() в код, чтобы понять, в каком месте происходит сбой.
Ошибку нашел, знать о ней не знал, догадался. В общем в XML файле все написал в строку, и все заработало, но так очень неудобно т.к. читаемость кода никакая, очень неудобно. Есть возможность писать все не в одну строку?
Ясно. Можно.
Дело в том, что в 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;
}
}
Огромное спасибо! Все отлично заработало, немного переделал под неограниченное количество загружаемых изображений за один подход, все как часы работает.
Еще раз спасибо!!!
И еще один момент, забыл про него. Как использовать стили в XML Допустим такой вид Получен файл бла бла бла байт Загружен успешно и рядом показываем само изображение
В результате не зеленая надпись не сама картинка не отображаются, и как я догадываюсь попросту не передаются из XML файла. Как это можно сделать?
Блин, скобки отсеялись (((
result
между результ и /результ можно ли вставить картинку и покрасить текст в какой-нибудь цвет?!
/result
Стили и оформление - это задача принимающего скрипта. 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]+”‘ />”;
Все, теперь все предельно ясно. Как плохо когда в чем-то дерево =). Большое спасибо за помощь, очень помогли!
В ближайшее время сажусь учить XML, полезная штука как я понял.
Здравствуйте, я решил этот метод реализовать на своем сайте, для добавления картинок, и столкнулся со следующей проблемой: При передачи таким методом картинок, происходит неправильное перекодирование изображений, в резултате чего картини получаются битыми, хотя с текстовыми файлами все нормально при передаче. В самом скрипте я еще ничего толком не менял, добавил только в uploadFile.php сохранение картинки… У меня такое ощущение что это дело в сервере, он почему-то перекодирует изображения… Подскажите куда копать?
borisovks,
Метод в принципе рабочий. Надо искать причину, почему у вас не получается.
Загрузите картинку сюда на тестовую страницу: http://web-tec.info/samples/sample_ajax_iframe_upload.htm
Посмотрите число полученных байт. Соответствует ли оно исходному числу байт в вашей картинке? Сравните с размером файла, который сохраняет ваш скрипт на вашем сервере.
Если размер совпадает, попробуйте побайтово сравнить файлы (переданный с полученным) чтобы понять характер изменений, которые происходят в процессе передачи.
Скопируйте сюда текст своего серверного скрипта.
Пока больше идей нет.
Спасибо вам за помощь, проблему я решил!
Оказывается меня коснулось “проклятье Русского апача”. В том то и дело файлы по размерам совпадали, долго не мог понять в чем подвох, оказалось дело в хостере и его злобном mod_charset
Зметил это не сразу, поскольу имею привычку всегда прописывать кодировку…
В общем после бессонной ночи гуглений решеиние было найдено через .htaccess добавлением туда
AddDefaultCharset Off
CharsetDisable On
CharsetRecodeMultipartForms Off
З.Ы. урок мне теперь на всю жизнь! :-)
Извините, но возникла еще одна проблемка… Без изменений скрипт отлично работает, но я решил доработать его, что-бы можно было получать несколько чайлдов, тоесть то же что добивался resort
Но возникает ошибка при отравке файла:
Method Not Allowed
The requested method POST is not allowed for the URL /up_ajax/sample.htm.
Хотя метод пост обязан работать, поскольку у меня еще много форм работают без ошибок, и все передаеют, стоило добавить …… и соответствующий код для принимающего скрипта, как полезло это… Если есть мысли, подскажите пожалуйста.
Вероятно POST должен быть направлен не на /up_ajax/sample.htm, а на php-скрипт. Проверьте параметр url, который передается в функцию sendForm.
Нет, там все нормально, как в примере. Тоесть добавился только блок кода для uploadComplete ну и для php скрипта, если верну обратно - то передает…
А откуда тогда взялась ссылка “/up_ajax/sample.htm”? Если я правильно понимаю, ваш веб-сервер ловит POST-запрос на эту ссылку и ругается в ответ, что htm-страницы не могут обрабатывать POST-запросы. Лично я с таким поведением не сталкивался, но вполне могу допустить его существование. В принципе логично.
Спасибо за ответ. Но вообще-то это была реакция на действия html страницы, которая располагается по такому адресу “/up_ajax/sample.htm”. Да и не может у меня взяться таких ссылок, поскольку файл обработчик лежит там же где и html форма. Ну и в url у меня точно прописан адрес обработчика…
Может дело таки в хостере
@admin
«Чтобы использовать uploadFile.php с нашего сервера нужно изменить код вызова sendForm(…,’http://web-tec.info/samples/uploadFile.php’,…)»
В настоящем виде способ не кросс-доменный, так не получится.