スポンサーリンク

JavaScriptの動かないコード (中級編) iframe内のDOM要素を別フレームにコピーできないエラー


以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


やりたい事:

  • 同じフォルダにtenki.htmlがあり,天気が書いてある。その情報を,DOM要素ごと抜き出す。


index.html

<body>

<input type="button" value="クリックして表示" onClick="f()"><br>


<!-- 天気を表示する。 -->
<iframe id="ifr" src="tenki.html"></iframe><br>


<table>
<tbody>
	<tr id="my_tr">
		<td>上のページの情報によれば,今日の天気は・・・</td>
	</tr>
</tbody>
</table>


<script language="JavaScript">

function f()
{
	// iframeの中のdocumentを取得
	var doc = document
		.getElementById( "ifr" )
		.contentWindow
		.document
	;
		// http://language-and-engineering.hatenablog.jp/entry/20090207/p1

	// 天気のノードを取得
	var e_node = doc.getElementById("tenki");
	
	// そのノードを複製
	var new_node = e_node.cloneNode( true );
	
	// そのノードを親フレームに表示
	document.getElementById( "my_tr" ).appendChild( new_node );
}

</script>

</body>


tenki.html

<table>
<tbody>
	<tr>
		<td>きょうの天気</td>
		<td id="tenki">晴れ</td>
	</tr>
</tbody>
</table>



答え



Firefoxでは問題なく動き,「今日の天気は・・・」のあとに「晴れ」と出る。


しかし,IEでは

ランタイムエラーが発生しました。デバッグしますか?

エラー:引数が無効です。

のエラーになる。




IEがどこでつまずくのか,alertをかけて調べてみると,appendChild()の所で失敗しているのがわかる。

その手前の cloneNode() は成功している。


原因

実はiframe越しにノードをコピーすることはできない

W3Cのサイト:Document Object Model FAQ
http://www.w3.org/DOM/faq.html#ownerdoc


ownerDocument issues

Must a Node always be owned by a specific Document?
Yes. DOM Level 1 decided that ownerDocument is set at the time the node is created, and never reset thereafter.

Why?
Different DOMs may implement Nodes in completely different ways, ...

How can I copy a node or subtree from one document to another?
DOM Level 2 defines an importNode() method that performs this operation.



(訳)


ownerDocument (ノードが所属するドキュメント)という問題について



 ノードは特定の一つのownerDocumentに属してないといけないんですね?

 →そうです。DOM Level 1 において,ノードの新規生成時点でそのノードにownerDocumentが設定され,その後からは変えられないというふうに決まりました。



 どうしてですか?

 →DOM(親ドキュメントの種類)が異なればノードの実装方法も全然変わってくるからです。...


 
 異なるドキュメント間でノードをコピーするにはどうするんですか?

 →importNode()を使ってください。



Mozillaのサイト:importNode() の使い方
https://developer.mozilla.org/ja/DOM/...


cloneNodeではなく,importNode() を使ってノードを「輸入」しなさいとのこと。


しかし,IEには importNode() はないので不可。

さらに冒頭のコードのように,FFでは importNode() を使わずとも,フレーム越しの cloneNode() がなぜか動いてしまう。(ややこしい)


その結果 importNode() の存在は忘れ去られ,
「フレーム越しの要素コピーは,FFではできて,IEではできないらしい」
と言われたりする。

IFRAMEからのDOMノードの移動
http://ongmap.com/blog/?p=479

DOMの仕様で(正確には「Recommendation」なので仕様ではないみたいだけど→ここ参照) 、異なるドキュメント間でのノードの移動はエラーになるみたい。

FireFoxではこのルールは強制していないみたいなんだけど、SafariとIEはしてるみたい。



IE6/Win DOMの要素追加系メソッドに制限?
http://members.jcom.home.ne.jp/jintri...

フレーム内のスクリプトにて、親ウィンドウのルート要素下にある要素をコピーし、そのフレーム内のルート要素下に追加しようとするとエラー(もちろんクロスサイトではなく)。



http://otd8.jbbs.livedoor.jp/javascri...
DOMメソッドでメモリ上に作成したノードは、
同じウィンドウ(フレーム)内でないと入れられないようです。

解決方法

冒頭のコードをクロスブラウザで動くようにするためには,少し回りくどくする必要がある。

cloneNode + appendChild の所を

	// 天気のノードを取得
	var e_node = doc.getElementById("tenki");
	

	// そのノードの中味を文字列としてコピー
	var str = e_node.innerHTML;
	
	// 新規ノードに反映
	var new_node = document.createElement("td");
	new_node.innerHTML = str;

	
	// そのノードを親フレームに表示
	my_tr.appendChild( new_node );

こんな風に,「文字列経由でのコピー」に書き変えてやればよいのだ。

(ただしこの場合,tdタグが保有していた各種属性は失われてしまう。)


jQueryを使えば多少簡潔になり,

	// 天気のノードを取得
	var str = $( "#tenki", doc ).html();
	
	// そのノードを親フレームに表示
	$( "#my_tr" ).append( "<td>" + str + "</td>" );

のように書ける。

※セレクタ部分の「doc」は,コンテキストを指定している。
http://semooh.jp/jquery/api/core/jQue...




iframe内にDOMアクセスできないエラー」の記事でも触れたが,インラインフレームは制限が多くていろいろと面倒くさい。


もしAjaxならば,外部からロードした内容を「自分と同じ土台に持ってこれる」ので楽だ。

そこが流行ったポイントの一つだろう。