NodeListの活用にはご注意を:DOMの基本を学ぼう(4)(2/2 ページ)
HTML上のタグ要素にアクセスする手法のおさらい。NodeListの活用法で気を付けるべき点、より実践的なHTMLでのメソッド使用法を学習しましょう。
コンテンツアクセスの実践
これまで、JavaScriptを使ってHTML上に存在するタグ要素にアクセスする手法を学んできました。しかし、説明に使ってきたサンプルは非常に簡単なHTMLですので、まだ実践的ではありません。ここでは、実際のHTMLにおいて、これまで学んできたメソッドをいかにして活用するのかを見ていきます。次のHTMLを例に、コンテンツアクセスの手法を見ていきましょう。
contents_access_sample1.html <body> <div> <h1>小説家一覧</h1> <div id="content"> <div id="japan"> <h2>日本</h2> <ul> <li>芥川龍之介</li> <li>江戸川乱歩</li> <li>夏目漱石</li> </ul> </div> <div id="uk"> <h2>イギリス</h2> <ul> <li>アーサー・コナン・ドイル</li> <li>アガサ・メアリ・クラリッサ・クリスティ</li> </ul> </div> </div> </div> </body>
このHTMLはDIVタグが何層にも重なっています。実際には、もっと多くの国や作家のリストが並んでいると仮定してください。
では問題です。このHTMLから"アーサー・コナン・ドイル"のLIタグ要素ノードオブジェクトを取り出すには、どうすればよいでしょう?
日本の作家が何人並んでいるのかはわからないとします。何人だったとしてもエラーにならないコーディングをしてください。また必ずしも、イギリスは日本の次にリストされているとは限らないと仮定してください。わかっているのは、イギリスの中では最初にリストアップされているということだけです。あなたならどうしますか?
このHTMLをよく眺めると、国全体をid属性が与えられたDIVタグで囲んでいることに気づくでしょう。これがとても役に立ちます。
var ukNode = document.getElementById('uk');
こうすることで、膨大なリストの中からイギリスの部分だけを抜き取ることができます。イギリスがHTML上のどの位置にあっても、それは一切気にする必要がありませんので、getElementByIdはとても便利なことがおわかりいただけるでしょう。ここまで範囲を限定できたところで、あとはLIタグ要素を抜き出せばよいのです。しかし、LIタグにはid属性がセットされていません。いまわかっているのは、対象の要素は最初のLIタグ要素であるということだけです。
var ukLiNodeList = ukNode.getElementsByTagName('li');
これでukNodeの中にある、2つのLIタグ要素を抜き出すことができました。ここまでくれば、最後の仕上げは簡単です。
var targetNode = ukLiNodeList.item(0);
これで完成です。
試しに次のコードを追加して、本当に"アーサー・コナン・ドイル"なのかを確かめてみましょう。
alert( targetNode.firstChild.nodeValue );
DOMスクリプティングでは、getElementById、getElementsByTagNameなどのメソッドを駆使して、いかに効率よくターゲットを特定するかが重要です。HTMLの書き方によってもターゲットの特定の仕方が大きく変わってしまいます。今回は、あえてid属性を一部のタグにセットしていたため、アプローチしやすい状況だったといえます。
もし、このサンプルに一切のid属性がセットされていなかったら、どうしますか? けっこう面倒なことになるのが想像できるでしょう。DOMスクリプティングをする際には、HTMLの書き方も非常に重要なポイントであることがおわかりいただけたかと思います。
では、もうひとつ難しい問題を出します。次のHTMLサンプルをごらんください。
<div id="authorsbox"> <div>芥川龍之介</div> <div>江戸川乱歩</div> <div></div> <div>夏目漱石</div> </div>
getElementsByTagNameメソッドを使わずに作家の名前をすべて取り出してください。
これは、作家名がDIVタグで囲まれているかどうかすらわからないと仮定した質問です。作家のリスト全体が、id属性に"authorsbox"がセットされたDIVタグで囲まれていることは、わかっているとします。しかし、このDIVタグがHTMLの中のどこに存在しているかという位置関係もわからないとします。また、1つだけ作家名が格納されていないDIVタグが存在します。
では、よく陥りがちなミスを見ていきましょう。
/*authorsboxの要素ノードオブジェクト*/ var authorsbox = document.getElementById('authorsbox'); /*作家名を格納する配列を初期化*/ var authors = new Array(); /*authorsboxの子要素を1つずつ繰り返し処理*/ for( var i=0; i<authorsbox.childNodes.length; i++ ) { /*子要素のノードオブジェクト*/ var node = authorsbox.childNodes.item(i); authors.push(node.firstChild.nodeValue); } /*結果をアラートウィンドウに表示*/ alert( authors.join("\n") );
一見、問題なさそうに見えますね。しかし、期待通りに実行されるWebブラウザは1つもありません。実は、authors.push(node.firstChild.nodeValue)がJavaScriptエラーを引き起こしていたのです。しかし、このコード自体、なにも問題はありません。
Internet Explorerにおいては、<div></div>を参照したときだけにエラーが発生していたのです。このコードは、DIVタグ内に記述されている作家名を抜き出して配列に追加する処理です。しかし、<div></div>は、子要素にテキストノードが存在していません。この場合、node.firstChildはnullとなります。nullに対してnodeValueプロパティを参照したためエラーになってしまったのです。
さらにもう1つ問題があります。Internet Explorer以外のWebブラウザでは、別の理由でエラーになっていたのです。それはホワイトスペースノードの存在です。
authorsbox.childNodesで得られるノードリストは、作家名が格納されているDIVタグ要素ノードだけではありません。これらDIVタグ要素の間に存在する改行やスペースの部分が、それぞれホワイトスペースノードとしてドキュメントツリー上に構成されます。当然、これらホワイトスペースノードは、子要素にテキストノードを持ちませんから、エラーになってしまうのです。
以上の問題をすべてクリアにするため、先ほどのスクリプトを改良してみましょう。このコードの前に次のコードを追加します。
if( ! node.hasChildNodes() ) { continue; }
もし子要素としてテキストノードが存在しなければ、hasChildNodesメソッドの戻り値はfalseになるはずです。もしfalseであれば、無視するようにしたのです。これで問題なく動作するようになりました。
今回のサンプルではこの改良だけで問題はありませんが、実践においてはさまざまな問題が起こります。本当にテキストノードが存在するのかどうかをもう少し厳密にチェックした方がよいといえます。次のコードは、考えうる問題に対処するためのエラーチェックを追加した完成版です。具体的になにをチェックすべきなのかをしっかりと学んでください。
contents_access_sample2.html /*authorsboxの要素ノードオブジェクト*/ var authorsbox = document.getElementById('authorsbox'); /*作家名を格納する配列を初期化*/ var authors = new Array(); /*authorsboxの子要素を1つずつ繰り返し処理*/ for( var i=0; i<authorsbox.childNodes.length; i++ ) { /*子要素のノードオブジェクト*/ var node = authorsbox.childNodes.item(i); /*ノードタイプが"要素"でなければ無視*/ if( node.nodeType != 1 ) { continue; } /*テキストノードを子要素に持っていなければ無視*/ if( ! node.hasChildNodes() ) { continue; } /*本当にテキストノードかどうかをチェック*/ if( node.firstChild.nodeType != 3 ) { continue; } /*テキストを抜き出して配列に格納*/ authors.push(node.firstChild.nodeValue); } /*結果をアラートウィンドウに表示*/ alert( authors.join("\n") );
正直なところ、かなり意地悪な質問と思われたことでしょう。しかし実践では、このレベルのスクリプティングが頻繁に求められます。以上のサンプルをしっかりと理解して、実践に役立ててください。
Copyright © ITmedia, Inc. All Rights Reserved.