Этот транспорт - пожалуй, самый универсальный и мощный, но и тонкостей в нем - больше всех
Для общения с сервером создается невидимый IFrame. Простая смена URL этого iframe - запрос к серверу за данными. Кроме того, в iframe можно отправлять post-запросы
поставив его имя в атрибут form.target.
Как правило, iframe - один, и запросы в него по очереди отправляются. Можно сделать и больше ифреймов, чтобы отправлять несколько запросов одновременно.
Однако, если их больше двух - придется выносить на разные поддомены. Об этом - в секции Обмен данными для документов с разных доменов.
iframeА теперь - к реализации и ее особенностям, включая преодоление описанных проблем!
iframe: окно+документЧто такое iframe? На этот вопрос у браузера два ответа
<iframe> со стандартным набором свойств
style.height, style.width и т.п.document.getElementsByName(name)[0] или document.getElementById(id)window
window.frames['имя фрейма']Когда мы говорим о переводе iframe на новый URL - подразумеваем "окно". Когда собираемся создавать его и запихивать в DOM - конечно, "тег".
В теге iframe хранится ссылка на окно. В зависимости от браузера, это либо iframe.contentDocument, либо iframe.contentWindow.document, либо iframe.document.
// получить окно по тегу
function getIframeDocument(iframeNode) {
if (iframeNode.contentDocument) return iframeNode.contentDocument
if (iframeNode.contentWindow) return iframeNode.contentWindow.document
return iframeNode.document
}
Из страницы внутри окна iframe можно пройти к родительскому окну через window.parent, и, если разрешает same origin policy, даже вызвать функцию/получить тег iframe.
iframeGET-запрос - всего лишь перевод iframe на новый URL. Его лучше всего осуществлять вызовом iframeDocument.location.src.replace(newURL).
Такой синтаксис в отличие от сабмита GET-формы в iframe или iframeDocument.location.src=..., оставляет history чистой в ряде браузеров. Так что переходы между адресами внутри iframe не отразятся на
кнопках back-forward и списке посещенных страниц..
function setIframeSrc(iframeNode, src) {
getIframeDocument(iframeNode).location.replace(src)
}
Для поста - достаточно задать форме form атрибут form.target='имя ифрейма' и вызвать form.submit(). Таким способом можно отправлять на сервер файлы, и вообще,
все что может содержать форма HTML.
// функция постит объект-хэш content в виде формы с нужным action, target
// напр. postToIframe({a:5,b:6}, '/count.php', 'frame1')
function postToIframe(content, action, target){
if(typeof phonyForm == 'undefined'){
// временную форму создаем, если нет
phonyForm = document.createElement("form")
phonyForm.style.display = "none"
phonyForm.enctype = "application/x-www-form-urlencoded"
phonyForm.method = "POST"
document.body.appendChild(phonyForm)
}
phonyForm.action = action
phonyForm.target = target
phonyForm.setAttribute("target", target);
// убить все содержание из временной формы
while(phonyForm.firstChild){
phonyForm.removeChild(phonyForm.firstChild);
}
// заполнить форму данными из объекта
for(var x in content){
var tn;
if(browser.isIE){
tn = document.createElement("<input type='hidden' name='"+x+"' value='"+content[x]+"'>")
phonyForm.appendChild(tn)
}else{
tn = document.createElement("input");
phonyForm.appendChild(tn);
tn.type = "hidden";
tn.name = x;
tn.value = content[x]
}
}
phonyForm.submit();
}
Сервер получает запрос, и генерирует в ответ страницу, которая, как правило, передает результат в основное окно через специальную функцию-каллбэк.
В примерах такая функция появится чуть позже, при рассмотрении невидимых ифреймов.
iframe и history
Для браузера iframe - такое же окно, как и основное. Соответственно, переходы в нем на разные URL должны попадать в историю, браузить туда-сюда можно через back/forward.
Например, вот:
Туда (GET)
Сюда (GET)
Сюда (POST)
Клик на Туда соответствует переходу по адресу there.html, а клик на Сюда - по адресу here.html (он же - начальный адрес).
Вы можете скопировать ссылку на этот пример и позаходить на нее в разных браузерах. Клики на кнопки туда-сюда никак не отражаются на истории, хотя
меняют страницу внутри фрейма. Пока что остается звук клика в IE. Запросы к серверу должны быть, вроде как, незаметны, так что придется от него избавляться.
Для начала проверим - загрязняется ли history. Для этого в новой вкладке(или окне) откроем урл примера. Затем, нажимая на кнопки - смотрим, не заработала ли
кнопка back. Если заработала - в history появился новый, лишний элемент. Лишний - потому, что автоматические, служебные переходы по специальным адресам
попадать в history не должны, они там лишние.
Кроме того, генерирует ли IE звук клика при переходе по служебному адресу? Как правило, при стандартной звуковой схеме Windows такой звук есть.
Результаты тестов зависят от браузера, ОС и т.п. Обычно получиться так, что при POST history загрязняется, а при GET - нет.
iframeСоздать ифрейм - так же просто, как и любой другой элемент. Пожалуй, единственная подстава - в IE свойство name должно обязательно задаваться при создании элемента.
Т.е, нельзя сначала сделать iframe, а потом присвоить ему name (то же самое и для input, и т.п.) - будут проблемы. Поэтому приходится делать отдельную проверку на isIE.
Приведенная ниже функция создает ифрейм с именем fname, если его еще нет, и при создании задает исходный адрес src. Если ифрейм с таким именем уже есть - он просто возвращается без дополнительных действий.
Если не задан параметр debug, то ифрейм после создания делается невидимым.
Динамическое создание ифрейма преследует две цели:
// браузер хранится в объекте browser
function createIFrame(fname, src, debug){
var ifrstr = browser.isIE ? '<iframe name="'+fname+'" src="'+src+'">' : 'iframe'
var cframe = document.createElement(ifrstr)
with(cframe){
name = fname // это не для IE
setAttribute("name", fname) // и это тоже, но вреда не будет
id = fname // а это везде ок
}
// можно добавлять сразу к document.body
document.getElementById('iframe_container').appendChild(cframe);
if (!debug) {
hideIframe(cframe)
}
if(!browser.isIE){
setIframeSrc(cframe, src);
}
return cframe
}
// прячем фрейм
function hideIframe(iframeNode) {
with(iframeNode.style) {
if(!browser.isSafari){
position = "absolute";
}
left = top = "0px";
height = width = "1px";
visibility = "hidden";
}
}
Вы можете самостоятельно протестировать влияние динамической генерации на звук клика в IE и кнопки back-forward.
Следующий пример работает только в IE. Он создает невидимый iframe, при запросе через который нет ни клика ни лишней хистори, ни индикатора загрузки.
Если Вы находитесь в IE, то можете заметить, что переходы по этому ифрейму абсолютно незаметны для пользователя. Идеал, да и только
. Он создается при помощи
слабо документированной, но безопасной возможности ActiveX. Никаких специальных опций браузера для нее включать не надо.
function handleMessage(txt) { // функция-обработчик сообщения с сервера, живет в основном окне
alert(">> "+txt) // для примера
}
// создать IE-only транспорт
function createIEFrame(fname, src) {
// создаем объект htmlfile, примерно аналогичный по функционалу window.document
var rcvNode = new ActiveXObject("htmlfile");
// заполним исходным HTML
rcvNode.open();
rcvNode.write("<html><head><title>ActiveX</title></head><body></body></html>");
rcvNode.close();
// связь htmlfile с родительским окном (см объяснения ниже)
rcvNode.parentWindow.deliver = handleMessage
// добавим внутрь div с нужным ифреймом
var ifrDiv = rcvNode.createElement("div");
rcvNode.appendChild(ifrDiv);
ifrDiv.innerHTML = "<iframe name='"+fname+"' src='"+src+"'></iframe>"
// глобальные переменные, для примера
// главное - чтобы объект ActiveX и фрейм были доступны из исходного окна
IEFrameNode = ifrDiv.firstChild
IEFrameDocument = rcvNode
}
Как видно из примера, для изоляции ифрейма создается промежуточный документ ActiveX. Поэтому операции над ифреймом и не видны из основного окна.Здесь получается, что объектов window не два, как обычно (основное окно + ифрейм), а три:
windowhtmlfile - доступно как IEFrameDocument.parentWindowIEFrameNode.contentWindow, или с использованием getIframeDocumentГлобальные переменные IEFrameNode, IEFrameDocument дают нам прямой доступ из основного окна в окна 2 и 3. Так что можно легко отправить запрос на сервер вызовом
setIframeSrc(IEFrameNode, '/server_push/parentHere.html')...
.. Но что дальше? Документ с сервера, наверное, захочет обратиться к основному окну. Например, вызвать его функцию handleMessage() с некоторым сообщением.
И здесь есть некоторые сложности. Чтобы вызвать функцию основного окна, javascript-код изнутри ифрейма должен пробиться через промежуточный htmlfile.
Но htmlfile - это отдельный HTML-документ. Политика безопасности same origin policy запрещает взаимный javascript-доступ между документами
с сайта и новосозданным htmlfile.
Чтобы такой доступ получить, нужно заранее, из основного окна, протянуть ссылку из окна htmlfile в основное окно:
// rcvNode: htmlfile, объект, сходный с document // rcvNode.parentWindow - окно, соответствующее rcvNode // делаем ссылку из этого окна на функцию из основного окна rcvNode.parentWindow.deliver = handleMessage
Если ссылка между окнами уже существует, то same origin policy не проверяется, так что документ с сервера может быть, например, таким:
<script>window.parent.deliver("here")</script>
Обмен данными проиллюстрирован на рисунке:

Если Вы решите использовать этот подход - позвольте мне сэкономить возможные часы копания в отладчике. До этого обсуждался метод GET.
В нем достаточно ссылки на iframe.
С другой стороны, в методе POST нужно присвоить form.target не сам иферейм, а имя ифрейма, причем этот ифрейм должен быть виден из текущего окна.
Легко проверить, что простой пост в фрейм "frame3" (в примере) не даст результата, т.к этот фрейм не виден.
// не пашет, откроет /server_push/parentHere.html в новом окне, т.к не увидит frame3
postToIframe({prop:'val'}, '/server_push/parentHere.html', 'frame3')
Для того, чтобы форма увидела ифрейм, ее нужно создать в том же окне. Это можно сделать массой способов. Например, добавить нужные скрипты в htmlfile:
...
rcvNode.open();
rcvNode.write("<html><head><title>ActiveX</title>")
rcvNode.write("<script src='/http/javascript.ru/js/browser.js'></sc"+"ript>")
rcvNode.write("<script src='/http/javascript.ru/server_push/iframe.js'></scr"+"ipt>")
rcvNode.write("</head><body></body></html>")
rcvNode.close();
...
И вызвать отправку формы из нужного окна:
IEFrameDocument.parentWindow.postToIframe({prop:'val'}, '/server_push/parentHere.html', 'frame3')
Все основное, надеюсь, в тексте есть. Если есть более глубокий интерес - приятного копания в отладчике
.
Описанный способ предлагает идеальную реализацию iframe-транспорта, которая, к сожалению, работает только в IE. А для остальных браузеров можно использовать либо обычный iframe-транспорт, либо другие транспорты.
Базовый пример использования iframe для COMET c использованием GET вы можете скачать.