iframe 関連処理まとめ

iframe 関連の処理で分かったことをこちらにまとめてこうかと思います。コードは jQuery ベースです。

変更履歴
2009.06.17
Safari,Chrome にて動的に生成したiframe内に CSS ファイルを読み込ます方法が分からないとしてましたが、codeなにがし(http://code.nanigac.com/forum/view/488)で教えていただき、記事を修正しました。

iframe 内 を親 html から参照する


同一ドメインであることが前提です。

//iframe 内の window オブジェクトを参照する
$('iframe')[0].contentWindow  

//iframe 内の document オブジェクトを参照する
$('iframe')[0].contents()[0]

//iframe 内の html 要素を参照する
$('iframe').contents().find('html')[0]

//iframe 内の head 要素を参照する
$('iframe').contents().find('head')[0]

//iframe 内の body 要素を参照する
$('iframe').contents().find('body')[0]

//iframe 内の div 要素を参照する
$('iframe').contents().find('div')[0]

iframe 内の script を実行する


子 html

function setBlue(){document.body.style.backgroundColor='blue'} 


親 html

$('iframe')[0].contentWindow.setBlue()

iframe の中身 を親 html から書き換える

動的に生成した iframe の中身 を書き換える
//iframe 生成
var ifm = $('body').append('<iframe/>').find('> :last-child');
var idoc = ifm.contents();

//iframe内ドキュメントの生成
idoc[0].open();
idoc[0].writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"')
idoc[0].writeln(
'<html>\
	<head>\
	<\/head>\
	<body>\
		test\
	<\/body>\
<\/html>');
idoc[0].close();

//div要素の追加
idoc.find('body').append('<div>add</div>');

//scriptファイルの読み込み
var sc = idoc[0].createElement('script')
sc.src = 'test01.js';
idoc.find('head')[0].appendChild(sc)

//scriptの実行
var sc = idoc[0].createElement('script')
sc.text = 'setTimeout(function(){document.body.style.backgroundColor="red"},1000)';
idoc.find('head')[0].appendChild(sc)
 
//cssファイルの読み込み
var cs = idoc[0].createElement('link')
cs.href = 'test01.css';
cs.type = 'text/css';
cs.rel = 'stylesheet';
idoc.find('head')[0].appendChild(cs)


head 要素ではなく body 要素に対し appendChild してるのは、ChromeSafari でエラーになるためです。
また、css ファイルの読み込みについては、ChromeSafari の場合はエラーにはなりませんが、css 定義内容が適用されません。
クロスブラウザで、css ファイルの読み込みと head 要素への挿入を可能にするには以下のようにするとできます。(動的に生成した iframe に対して実現する方法が分かりません。)

事前に用意してある html(iframe の中身) を親 html から書き換える

//htmlファイル を読み込む
var ifm = $('body').append('<iframe src="template.html"/>').find('> :last-child');

//読み込み完了したら内容を変更する
ifm.load(function(){

	var idoc = $(this).contents();
	var ihead = idoc.find('head');

	//bodyのinnerHTML書き換え
	idoc.find('body').html('test');
	idoc.find('body').append('<div>add</div>');

	//scriptファイルの読み込み
	var sc = idoc[0].createElement('script')
	sc.src = 'test01.js';
	ihead[0].appendChild(sc)

	//scriptの実行
	var sc = idoc[0].createElement('script')
	sc.text = 'setTimeout(function(){document.body.style.backgroundColor="red"},1000)';
	ihead[0].appendChild(sc)

	//cssファイルの読み込み
	var cs = idoc[0].createElement('link')
	cs.href = 'test.css';
	cs.type = 'text/css';
	cs.rel = 'stylesheet';
	ihead[0].appendChild(cs)
});

iframe で Ajax する

iframe で Ajax した場合、以下のようなメリットがあります。


私の場合「サーバサイドの事情で Shift_JIS でしか出力できないけど Ajax を適用したい」というケースが多いので、Ajax はほとんど iframe ベースです。

即席プラグイン

$.fn.iframeAjax = function(callback){
	var self = this;
	self.each(function(idx){
		var ready = 0;
		var o = self.eq(idx);
		var target = 'iframeAjax'+($.fn.iframeAjax.cnt++);
		o.attr('target',target);
		var ifm = $('body').append('<iframe name="'+target+'"/>').find('> :last-child').hide();
		return ifm.load(function(){
			if((ready || !(jQuery.browser.msie || jQuery.browser.mozilla)) && callback){
				callback.apply(this,[$(this).contents()])
			}
		ready = 1;
		})
	});
}
$.fn.iframeAjax.cnt = 0;

使用例

<a href="calc.jsp?date=123">iframeAjaxTest</a>
<form method="post" action="calc.jsp">
	<input name="data"/>
	<input type="submit" value="send"/>
</form>

$('a,form').iframeAjax(function(doc){
	$('body').prepend('<div>'+doc.find('#result').html()+'</div>')
});

処理の流れ

  • a や form 要素のように get や post が発生する要素に対し、target 設定した iframe を生成します。
  • get / post が発生すると iframe の load イベントが発火されるので、この中で callback 関数を実行します。
  • この時処理結果として iframe 内 の document オブジェクトを callback 関数に渡してます。
  • callback 関数では、この document オブジェクトを起点に find メソッド等を使用して、欲しい処理結果(要素)を取得します。

実装上のポイント


callback 関数を実行するため iframe の load イベントを設定してますが、このイベントは IEFirefox の場合のみ iframe を body 要素に append したタイミングでも動いてしまいます。ですので append のタイミングでは callback 関数を実行しないようにフラグ(ready)にて制御してます。

target 属性の名前がダブらないように下記のように処理してます。

var target = 'iframeAjax'+($.fn.iframeAjax.cnt++);


ただこのようにしても複数の iframe を使用し、それぞれのフレーム内で上記のプラグインを使用すると重複した名前が適用される可能性があります。この場合異なる iframe 間での重複なので問題なさそうな気もしますが、正しく動作してくれません。なので汎用的に使用する場合は、target 名には、タイムスタンプやランダム値などを織り交ぜた方が安心して使えると思います。