Javascript.RU

AJAX-транспорт IFrame

Update: Более новый материал по этой теме находится по адресу https://learn.javascript.ru/ajax-iframe.

Этот транспорт - пожалуй, самый универсальный и мощный, но и тонкостей в нем - больше всех

Для общения с сервером создается невидимый IFrame. Простая смена URL этого iframe - запрос к серверу за данными. Кроме того, в iframe можно отправлять post-запросы
поставив его имя в атрибут form.target.

Как правило, iframe - один, и запросы в него по очереди отправляются. Можно сделать и больше ифреймов, чтобы отправлять несколько запросов одновременно.
Однако, если их больше двух - придется выносить на разные поддомены. Об этом - в секции Обмен данными для документов с разных доменов.

  • Возможна отправка файлов пользователя с диска: POST формы в iframe.
  • Звук "клика" при переходе запросе в iframe
  • Изменения в history браузера, влияющие на историю посещенных страниц и/или кнопки back-forward. Переходы по служебным URL не должны отражаться на history.
  • Полоса загрузки или курсор-часики при запросе в iframe. Запросы должны быть по возможности прозрачны, невидимы для посетителя.

А теперь - к реализации и ее особенностям, включая преодоление описанных проблем!

Что такое iframe? На этот вопрос у браузера два ответа

  1. HTML-тэг: <iframe> со стандартным набором свойств
    • Тэг можно создавать в javascript
    • У тега есть стили, можно менять style.height, style.width и т.п.
    • К тегу можно обратиться через document.getElementsByName(name)[0] или document.getElementById(id)
  2. Окно браузера, window
    • Такое же по функционалу окно браузера, как и основное, с адресом и т.п.
    • Основное окно и ифрейм могут общаться через javascript, если находятся на одном домене, или на разных поддоменах одного домена 2 уровня (same origin policy).
    • Можно получить через 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.

GET-запрос - всего лишь перевод 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)
}

POST

Для поста - достаточно задать форме 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 - такое же окно, как и основное. Соответственно, переходы в нем на разные URL должны попадать в историю, браузить туда-сюда можно через back/forward.
Например, вот:

Туда (GET)
Сюда (GET)
Сюда (POST)

Клик на Туда соответствует переходу по адресу there.html, а клик на Сюда - по адресу here.html (он же - начальный адрес).

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

Для начала проверим - загрязняется ли history. Для этого в новой вкладке(или окне) откроем урл примера. Затем, нажимая на кнопки - смотрим, не заработала ли
кнопка back. Если заработала - в history появился новый, лишний элемент. Лишний - потому, что автоматические, служебные переходы по специальным адресам
попадать в history не должны, они там лишние.

Кроме того, генерирует ли IE звук клика при переходе по служебному адресу? Как правило, при стандартной звуковой схеме Windows такой звук есть.

Результаты тестов зависят от браузера, ОС и т.п. Обычно получиться так, что при POST history загрязняется, а при GET - нет.

Создать ифрейм - так же просто, как и любой другой элемент. Пожалуй, единственная подстава - в IE свойство name должно обязательно задаваться при создании элемента.
Т.е, нельзя сначала сделать iframe, а потом присвоить ему name (то же самое и для input, и т.п.) - будут проблемы. Поэтому приходится делать отдельную проверку на isIE.

Приведенная ниже функция создает ифрейм с именем fname, если его еще нет, и при создании задает исходный адрес src. Если ифрейм с таким именем уже есть - он просто возвращается без дополнительных действий.

Если не задан параметр debug, то ифрейм после создания делается невидимым.

Динамическое создание ифрейма преследует две цели:

  1. Делаем HTML чище
  2. Динамический ифрейм - еще одна мера против загрязнения history
// браузер хранится в объекте 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 не два, как обычно (основное окно + ифрейм), а три:

  1. Основное окно window
  2. Окно документа htmlfile - доступно как IEFrameDocument.parentWindow
  3. Окно ифрейма - доступно как IEFrameNode.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>

Обмен данными проиллюстрирован на рисунке:

POST в ActiveX -> iframe

Если Вы решите использовать этот подход - позвольте мне сэкономить возможные часы копания в отладчике. До этого обсуждался метод 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='/js/browser.js'></sc"+"ript>")
rcvNode.write("<script src='/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 вы можете скачать.


Автор: destresS, дата: 23 мая, 2008 - 19:04
#permalink

Iframe History победить легко : только POST, а после на сервере header location

Функция для создания IFRAME:

var IFRAME = function(parent){
	var iframe = document.createElement("iframe");
	parent.appendChild(iframe);
             iframe.src = 'javascript:;' // обязательно для IE6,7?, если не указать, свойство frameBorder недоступно
	var doc;
	if(iframe.contentDocument) doc = iframe.contentDocument;
	else if(iframe.contentWindow) doc = iframe.contentWindow.document;
	else if(iframe.document) doc = iframe.document;
	if(doc == null) return null;
	doc.open();
	doc.close();
	return [iframe, doc];
};

Автор: NT Man, дата: 11 августа, 2009 - 22:45
#permalink

Можно поподробнее про POST, GET побеждаю, благодаря динамично создаваемому IFRAME.


Автор: destresS, дата: 23 мая, 2008 - 19:03
#permalink

также добавлю уплоад на jQuery:

jQuery.iframeUploader =	{

	instances: {},

	build: function(o) {
		return this.each( function(){
			jQuery.iframeUploader.make(this, o);
		});
	},

	make: function(e, o){
		var i = $.addIFRAME(e);
		var doc = i[1], iframe = i[0];
		iframe.style.width = (o.iframeWidth || "250") + "px";
		iframe.style.height = (o.iframeHeight || "150") + "px";
		var s = '<html><body bgcolor="#F0F0EE"><style> @import url("css/iframe.upload.css");</style><form action="' + o.url + '" method="post" enctype="multipart/form-data">'
			+'<input type="file" class="file" name="file" /><div class="dv">'
			+'<input type="submit" class="button" value="Upload" /></div>';

		if(o.post) for(var i in o.post){
			s += "<input type='hidden' name='" + i + "' value='" + o.post[i] + "' />";
		}
		s += '</form></body></html>';

		if($.browser.msie){
			iframe.frameBorder = "0";
			document.frames[0].frameElement['src'] = "_blank";
			doc.write(s);
			doc.close();
		}
		else
			doc.body.innerHTML = s;

		var f = $(doc.body).find("form")[0];

		if(o.instance) {
			var i = {
				onBefore: o.onBefore,
				onUpload: o.onUpload
			};
			jQuery.iframeUploader.instances[o.instance] = i;
			$.iframeUploader.registerEvents(f, o.instance);
		}
	},

	registerEvents: function(form, instance){
		var t = this;
		form.onsubmit = function(e){
			var file = $(this).find("input[@name=file]");
			if(file)
				return t.fire("onBefore", instance, file[0].value);
			return true;
		}
	},

	fire: function(event, instance, params){
		var i = $.iframeUploader.instances;
		for(var j in i){
			var f = i[j][event];
			if(j == instance && typeof f == 'function'){
				return f(instance, params);
			}
		}
	}
};

jQuery.fn.extend({ iframeUploader: jQuery.iframeUploader.build });

Автор: Павлег (не зарегистрирован), дата: 14 ноября, 2008 - 17:43
#permalink

Вот такая проблема. Нигде не могу найти ответ. Если в айфрейм используешь ссылку на чужой сайт. То в айфрейм страница подгружается с левого верхнего угла. А иногда необходимо чтоб окно подгружалось из середины или после отступа 100 пикселей сверху, Или что самое правельное вставить в айфрем поиск который по образцу найдет нужный участок на странице и подгрузит начиная с него. Задача мне показалась сначала элементарной вствить бы скрипт поиска внутрь окна и вбить вместо переменной постоянную, однако почему то не вышло ничего.


Автор: Гость (не зарегистрирован), дата: 4 ноября, 2009 - 08:42
#permalink

В этом ключе сделал прогу, но... работает наполовину. Кто подскажет- почему и как лучше сделать?

Имеется:
html, в нем десяток узлов javascript и <div id=a1 style='hiden'> разное &lgt;/div>

Делается:
1) Первый script через document.getElementById('a1').innerHTML забирает "разное" и рисует формы
<form id=a2> <textarea id=textar>разное-2</textarea></form>
и вторую <iframe name=fout1 id=a3> </iframe>
где "разное-2"=F("разное"+KB) - всё делается ОК

2) Второй script через document.getElementById('textar').innerHTML забирает "разное-2" и <post ...php... target=fout1> - всё делается ОК -> на php попадает "разное-2", и через секунду fout1 заполняется ответом "bla-bla-bla"

3) Третий script пробует:
var str1=document.getElementById('a3').innerHTML
var str1=document.getElementById('a3').value (глупость, но пробует)
var str1=document.getElementById('fout1').innerHTML
и др...
- пытается выковырнуть из из fout1 "bla-bla-bla" через 10 секунд после post , но ничего не получается. В чем ошибка?

Как ещё можно реализовать функцию var str1= ...(ответ от php на post-запрос), либо выбить из php в java-функцию результат на запрос "разное-2" по протоколу post (или иным образом (каким?). Есть рабочий пример?


Автор: Joomla master (не зарегистрирован), дата: 12 декабря, 2009 - 11:49
#permalink

Хех, приятноу видеть эту статью, когда-то делал чат(вернее онлайн систему сообщений) на ajax - принцип очень похож на этот


Автор: E1 (не зарегистрирован), дата: 5 февраля, 2010 - 01:35
#permalink

У меня в опере (версия 10.10) оба примера - с динамическим и статическим iframe [#history2 и #history1]- загрязняют хистори при использовании метода POST =(


Автор: Гость (не зарегистрирован), дата: 7 мая, 2010 - 23:10
#permalink

Спасибо огромное, красавчик!
Все нубы никто, нормально объяснить не может, даже пухлые книги!
А ты еще и кучу проблем сразу решил.


Автор: amigo*, дата: 5 июня, 2010 - 16:15
#permalink

Звук "клика" при переходе запросе в iframe. Наверное, .. при переходе запросА в iframe


Автор: rewlad (не зарегистрирован), дата: 6 сентября, 2010 - 11:40
#permalink

input value в ie не экранирован


Автор: Гость (не зарегистрирован), дата: 19 октября, 2010 - 22:09
#permalink

Динамически созданный iframe меняет строку статуса IE, а это заметно пользователю. Если сделать его невидимым то еще и выдает ошибку "Отказано в доступе"м(при настройках браузера по умолчанию).


Автор: _Никита (не зарегистрирован), дата: 22 октября, 2010 - 16:14
#permalink

Если ссылка между окнами уже существует, то same origin policy не проверяется, так что документ с сервера может быть, например, таким:

window.parent.deliver("here")

Пробовал:
parentWindow.deliver
parent.parent.deliver
parentWindow.parentWindow.deliver
window.parent.deliver
и много чего прочего
Сколько не пытался пробиться из ифрейма, ничего не получается. Если открывать серверный скрипт в браузере, при просмотре исходного кода страницы вижу ответы сервера. Но ни в какую не вызывается функция главного окна указанная как rcvNode.parentWindow.deliver = handleMessagе

Кто нибудь пробовал этот пример в деле, в чем может быть проблема?


Автор: IgorN, дата: 6 ноября, 2010 - 19:29
#permalink

В IE(6,7) теряются куки, как побороть кто знает?


Автор: ajon, дата: 15 декабря, 2010 - 10:01
#permalink

>> rcvNode.parentWindow.deliver = handleMessage;

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


Автор: motopolis (не зарегистрирован), дата: 5 января, 2011 - 20:47
#permalink

Спасибо


Автор: golubevod (не зарегистрирован), дата: 7 января, 2011 - 03:10
#permalink

Все хорошо расписано!


Автор: gunsvd (не зарегистрирован), дата: 7 января, 2011 - 21:01
#permalink

Автор, Вы достойны уважения!


Автор: newsoftreview (не зарегистрирован), дата: 11 января, 2011 - 03:14
#permalink

Оживленно написано, заставляет задуматься! спасибо


Автор: urbancruiser (не зарегистрирован), дата: 12 января, 2011 - 00:04
#permalink

Спасибо


Автор: Spazm (не зарегистрирован), дата: 21 января, 2011 - 15:52
#permalink

Все вроде работает.... тока вопрос - как сделать чтобы значек закрузки постоянно не висел на вкладке браузера7 бесит((( его реально убрать как-нить7


Автор: Гость (не зарегистрирован), дата: 26 марта, 2011 - 12:22
#permalink

частенько замечал в ИЕ нестандартное поведение - когда firstChild=null, а lastChild=[object], т.е при наличии у родителя 1 CHILD`а атрибута firstChild - нет, lastChild - есть!

поэтому этот код с БАГОМ
// убить все содержание из временной формы
19 while(phonyForm.firstChild){
20 phonyForm.removeChild(phonyForm.firstChild);


Автор: Гость (не зарегистрирован), дата: 14 июня, 2011 - 13:02
#permalink

Как все таки победить проблему "отказано в доступе"


Автор: Гость (не зарегистрирован), дата: 23 октября, 2011 - 21:39
#permalink

Индикатор загрузки есть в ff7, chrome, дазнт верк короче..


Автор: maximamus (не зарегистрирован), дата: 1 марта, 2012 - 16:21
#permalink

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


Автор: Нервный (не зарегистрирован), дата: 7 марта, 2012 - 15:53
#permalink

Отлично все расписано. Благодарен!


Автор: Владислав2012 (не зарегистрирован), дата: 30 мая, 2012 - 14:09
#permalink

Вопрос - есть ли библиотеки js для реализации поддержки (в т.ч. кросс-браузерной) описанного в этой статье функционала?


Автор: Гость (не зарегистрирован), дата: 8 августа, 2012 - 15:54
#permalink

В абзаце про #GET: "GET-запрос - всего лишь перевод iframe на новый URL. Его лучше всего осуществлять вызовом iframeDocument.location.src.replace(newURL)." - здесь должно быть iframeDocument.location.replace(newURL) (src лишний).


Автор: Гость (не зарегистрирован), дата: 29 января, 2013 - 18:41
#permalink

Подскажите пожалуйста, а как реализовать что б в iframe открывался сайт но с условием если xmlhttp.status == 200, в противном случае браузер повторял бы попытку? Т.е. если сервер сильно нагружен что б не приходилось вручную долбить F5 а просто автоматизировать этот процесс


Автор: skyfox (не зарегистрирован), дата: 19 сентября, 2013 - 21:59
#permalink

хороший ресурс


Автор: Гость (не зарегистрирован), дата: 4 октября, 2013 - 01:51
#permalink

И кому нужен этот IE? Как предотвратить изменение истории в Firefox, Chrome и Opera (на звук щелчка и индикатор загрузки наплевать)?


Автор: Гость (не зарегистрирован), дата: 9 февраля, 2014 - 15:59
#permalink

Скажите, откуда отправлять POST, из главного окна или фрейма? Как при этом оставлять открытым COMET - соединение? или нужно переподключаться?


Автор: Гость (не зарегистрирован), дата: 28 марта, 2022 - 12:53
#permalink

Надо понимать, хотя бы в общих, что такое формальные языки, формальные грамматики, контекстно-свободные грамматики, лексический анализ и т.д. Можно почитать в википедии, например: Super Mario Bros


 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние темы на форуме
Forum