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=479DOMの仕様で(正確には「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ならば,外部からロードした内容を「自分と同じ土台に持ってこれる」ので楽だ。
そこが流行ったポイントの一つだろう。