JavaScript を使って DOM をトラバースする

DOM スクリプティングと JavaScript ライブラリーを使用してメリットをもたらす

Web 開発者であれば当然、JavaScript と DOM (Document Object Model) に慣れ親しんでいるはずです。DOM は XML/HTML 文書を抽象化する、言語に中立なインターフェースとなる一方、JavaScript は、開発者がこのインターフェースを使用して Web ページと対話するための実装となります。この記事を読んで、DOM の JavaScript バインディングについて詳しく探り、最高のパフォーマンスで Web 文書を操作する方法を学んでください。この記事ではサンプル・アプリケーションを用いて DOM のメソッドとプロパティーを説明し、さらにはハンドラーを DOM イベントに関連付ける方法も説明します。

Sebastiano Armeli-Battana, Software Engineer, Freelance

Sebastiono Armeli BattanaSebastiano Armeli-Battana は、JavaScript および Java 開発を専門とするソフトウェア・エンジニアです。Web 技術に情熱を傾けている彼は、Java 技術を採用している SMS Management & Technology でコンサルタントを務める傍ら、フリーランスの Web エンジニアとしても働いています。彼の著書には、『jQuery plug-in JAIL』があります。彼個人のサイトは http://www.sebastianoarmelibattana.com です。



2011年 8月 19日

はじめに

DOM (Document Object Model) は W3C (World Wide Web Consortium) により、異なる仕様グループ (DOM Level 1、DOM Level 2、および DOM Level 3) で定義されています。DOM は、HTML または XML 文書を、プロパティーとメソッドを持つノードの階層からなるツリーとして表現します。JavaScript などのクライアント・サイドの言語を使用してツリー内のノードに対する追加、変更、削除、そしてイベントの関連付けを行うことで、インタラクティブで動的な Web ページを生成することが可能になります。

クライアント・サイド・スクリプト言語 (JavaScript) で DOM を変更することを、DOM スクリプティングと呼びます。Web 開発では、HTML、CSS、および JavaScript によってインタラクティブな Web ページを作成することを DHTML (Dynamic HTML) と総称してきましたが、DOM スクリプティングという言葉は、この DHTML の代わりに使用されます。

この記事では、DOM API で最もよく使われているメソッドと属性について詳しく探ります。詳細な例を用いて JavaScript で DOM をトラバースする方法を説明し、さらに複雑なモデルでは、どのような場合にイベントとリスナーが考慮されるかを説明します。また、JavaScript ライブラリーを利用して DOM を操作する方法を学んでください。

記事で使用するソース・コードは、ダウンロードすることができます。また、記事で取り上げる概念についてさらに深く調べられるように、「参考文献」にリンクを記載してあります。


DOM スクリプティング

DOM の用語では、文書は「ツリーのルート」として表され、JavaScript でこれに該当するのは、window.document です。あるいは単に document と表現されることもあります (文書は Window オブジェクトに関連付けられるためです)。一部の JavaScript 実装では、このオブジェクトが出発点となります。リスト 1 に、HTML フラグメントの一例を記載します。

リスト 1. HTML コード
<body>
   <p id="paragraph1">
      <span>This is some text</span>
      <a href="/index.html" title="Click here">Click here</a>
   <p>
</body>

DOM の見方では、上記の例で使用されている p タグは DOM の Element インターフェースで表現されます。これは、span タグおよび a タグの親です。span タグと a タグは兄弟の関係にあります。

例えば、リスト 1 のコードで使われているアンカーの href 属性を取得するとします。DOM 内の要素には、getElementById メソッドを使用すれば簡単にアクセスすることができます。Element getElementById (in DOMString elementId) というコードは、IDL (Interface Definition Language) で記述した getElementById シグニチャーを含む document インターフェースの定義部分を表しています。

JavaScript は String オブジェクトを使って DOMString インターフェースを実装するため、このメソッドは、ストリング形式の要素 ID をパラメーターとして受け入れます。上記の例で p タグには唯一 id 属性があるので、var paragraph = document.getElementById("paragraph1"); を使用すれば、この要素を取得することができます。

p タグ内にネストされたアンカーを取得するには、childNodes 属性を使用します。この属性は Node インターフェースに属し、NodeList 型のオブジェクトを返します。このオブジェクトは、JavaScript のオブジェクトのなかでも配列に似たオブジェクトで、pop()push() などのメソッドはありませんが、length プロパティーがあります。childNodes 属性から返されるオブジェクトは、ノード要素 (HTML タグ)、テキスト・ノード、またはコメント・ノードを区別しません。単にノード要素を探しているというのであれば、children 属性を使用するという方法もあります。この属性を使えば、テキスト・ノードやコメント・ノードのことが考慮されないため、childNodes よりも素早く要素を取得することができます。この例では、アンカーはパラグラフの 2 番目の子となっているので、var aElement = paragraph.children[1]; を使用して取得することができます。

ある要素の href 属性の値を取得するには、getAttribute メソッドを適用して、属性の名前 (この例では href) をパラメーターとして渡します。IDL 定義での getAttribute メソッドが含まれる部分は、DOMString getAttribute (in DOMString name) です。

この例では、上記のインターフェースを var aHref = aElement.getAttribute("href"); // "index.html" として実装することができます。

JavaScript での場合と同じく、複数のメソッドをつなげることもできます。a タグの href 属性の値を 1 行のコードで取得するには、var aHref = document.getElementById("paragraph1").children[1].getAttribute("href"); // index.html */ を使用してください。


DOM スクリプティングの詳細: サンプル・アプリケーション

このセクションでは、DOM スクリプティングの機能をいくつか取り上げて詳しく説明します。サンプルとして使用する Sticky Notes アプリケーションは、ユーザーがページをリロードせずに付箋メモを追加することができるインタラクティブな Web ページで、図 1 のように表示されます。

図 1. Sticky Notes アプリケーションのフロントエンド
Sticky Notes アプリケーションのフロントエンド

リスト 2 に、図 1 に示したページの HTML コードを記載します。head タグ内に指定されているのは、CSS ファイルと JS ファイルへの参照です。body タグの内容を見るとわかるように、ページに表示されているメモは、textarea タグと、新規メモの作成をトリガーするアンカーで構成されています。

リスト 2. HTML コード
<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8">
        <title>
            Dom Scripting
        </title>
        <link rel="stylesheet" href="css/master.css" />
        <script src="js/script.js"></script>
    </head>
    <body>
        <div class="wrapper">
            <h1> Sticky Notes </h1>
            <div class="links">
                <textarea id="contentArea" cols="10"> </textarea>
                <a href="/random.html" class="add">Click here</a> 
<span>to add a sticky note</span>
            </div>
            <div id="notes">
                <div class="note">
                    <p>
                        This is a note
                    </p>
                </div>
            </div>
        </div>
    </body>
</html>

このページがロードする script.js ファイル内の JavaScript コードについて、分析していきましょう。スクリプトのロジックは、ページのロードが完了した時点、または文書が作成された時点でトリガーする必要があります。そのための 1 つの方法は、関数を onload ウィンドウ属性にバインドすることです (リスト 3 を参照)。

リスト 3. onload 属性
window.onload = init;
function init() {}

onload 属性は、DOM のロード・イベントに関連付けられます。DOM Level 0 でイベントをリスナー関数にバインドするには、これが通常の方法です。DOM Level 0 は、すべてのブラウザーでサポートされる「仕様」ですが、標準ではありません。その一方、DOM Level 2 仕様には標準 DOM イベント・モデルが定義されています。この仕様では、イベント・ハンドラーをターゲット要素に登録するためのメソッドとして、(EventTarget インターフェースの) addEventListener を定義しています。このメソッドのシグニチャーは、object.addEventListener(eventType, eventHandler, useCapture); です。

eventType はオブジェクトに登録するイベントで、eventHandler は特定のイベントにバインドする関数です。useCapture はオプションのブール値で、イベント・フローのどのフェーズ (キャプチャー・フェーズまたはバブリング・フェーズ) で関数を呼び出すかを定義します。addEventListener 関数を使用して load メソッドをウィンドウにバインドするためのコードは、window.addEventListener("load", init, false); です。

残念ながら、バージョン 9 より前の Internet Explorer (IE) は上述の W3C メソッドをサポートしていません。代わりに、独自の実装として object.attachListener(eventType, eventHandler); を使用します。IE がサポートする DOM Level 3 イベントについては、「参考文献」を参照してください。

eventType では、イベント名に接頭辞 on を付ける必要があります。IE のイベントはデフォルトでバブリングを行うため、useCapture パラメーターはありません。

リスト 4 に、script.js から抜粋した addEvent 関数を記載します。すべてのブラウザーでイベント・バインディングを処理するこの関数は、SA という名前のグローバル・オブジェクトのメソッドです。このメソッドは、これまでに説明したすべての手法で使用することができます。

リスト 4. addEvent 関数
window.SA  = {
addEvent : function(element, evType, fn, useCapture) { 
        if (element.addEventListener) { 
            element.addEventListener (evType, fn, useCapture); 
            return true; 
        } else if (element.attachEvent) { 
            var r = element.attachEvent('on' + evType, fn); 
            return r; 
        } else { 
            element['on' + evType] = fn; 
        } 
    }
}

addEvent 関数を使用する場合には、load イベントに関数 (ここでは SA.load という関数にします) をバインドすることができます (リスト 5 を参照)。

リスト 5. 関数をバインドする
SA.addEvent(window, "load", SA.load, false);
SA = {
...
           load : function() {
                       // init block
           }
}

上記の SA.load 関数は、load イベントに関連付けられていることから、すべてのリソースがダウンロードされた場合にのみ呼び出されます。一般的なシナリオでは、load イベントに関連付けられた関数は実行されるまでに多少時間がかかります。これは、ページに多数の画像がダウンロードされる場合には尚更のことです。そこで適切なプラクティスとなるのが、スクリプトを初期化する関数を DOMContentLoaded イベントに関連付けることです。最近のブラウザーでサポートされているこのイベントは、DOM が作成されるとトリガーされます。スクリプトを初期化する関数は、外部リソースがダウンロードされる前に実行されるため、ページの応答性が良くなります。バージョン 9 より前の IE には、すぐに使用できる DOMContentLoaded イベントが組み込まれていないため、他のブラウザーと同じような動作をさせるには何らかの手段が必要です。ただし、この例ではページに画像がないので、このロード手法をそのまま使用することができます (ページのパフォーマンスが大幅に影響されることはありません)。

これで、関数ハンドラーをターゲット・アンカーの click イベントに関連付ける用意ができました。そこで、ユーザーがアンカーをクリックすると、特定の振る舞いが実行されるようにします。この例でその振る舞いに該当するのは、新しいメモの作成です。そのための最初のタスクは、DOM をトラバースして、ターゲットとしているアンカーを取得します (リスト 6 を参照)

リスト 6. add をクラス名に持つアンカーを取得する
load : function() {
  var anchorSelected;
        
  if (document.getElementsByClassName) {
    anchorSelected = document.getElementsByClassName("add")[0];
  } else {
    var anchors = document.getElementsByTagName("a"),
        alenght = anchors.length;
        
    for (var i = 0; i < alenght; i++ ) {
      var anchor = anchors[i];
            
      if (anchor.className === "add") {
      anchorSelected = anchor;
    }
    }
  }
}

リスト 6 の document.getElementsByClassName メソッドは、おそらく予想できると思いますが、クラス名を指定して要素を取得するために使用します。このメソッドは一連の HTML 要素を返しますが、残念ながらすべてのブラウザーがこのメソッドを完全にサポートしているわけではありません。それに該当するのは、例えば、IE6 と IE7 です。このようなブラウザーには、別のロジックを作成しなければなりません。別のロジックとしては、最初に document.getElementsByTagName メソッドによってアンカーのリストを取得し、そのリストをループ処理することで、add という CSS クラス名のアンカーを取得することができます。getElementsByTagName メソッドが正式に返すのは NodeList オブジェクトです。このオブジェクトは幸い、すべての主要なブラウザーで完全にサポートされています。

リスト 6 には、アンカーの配列のサイズを alength 変数に格納する方法が示されています。こうすれば、for-loop で DOM に対して一度問い合わせをするだけで済みます。DOM を変更した上で操作するのはコストのかかる処理なので、DOM を操作する回数はできるだけ少なくしてください。

後はアンカーを取得すれば、メモの追加を処理するリスナー関数に click イベントをバインドすることができます (リスト 7 を参照)。

リスト 7. イベントをバインドする
load : function() {
  ...
  SA.addEvent(anchorSelected, "click", SA.addNote, false);
}

リスト 7 には、click イベントに関連付けられたイベント・リスナーの名前は、SA.addNote であることが示されています。この関数には以下の目標があります。

  • 最後に作成されたメモを複製すること
  • 複製したメモに、ユーザーが入力したテキストを注入すること
  • メモのリストに、新しく作成されたメモを追加すること

リスト 8 に、最初の目標を達成するための実装を記載します。

リスト 8. 最後に作成されたメモを複製する
addNote : function(event) {
     var notes = document.getElementById("notes");
        
     // Clone the node
     var newNode = notes.children[0].cloneNode(true);
},

getElementById メソッドでメモ ID を基準に div タグを取得した後は、この div タグ内部にネストされた最初の子要素を取得し、cloneNode メソッドを使用してその要素を複製します。複製した DOM ノードは、newNode という変数に格納します。

次に、newNode 内にネストされたパラグラフ・ノードを選択し、複製したノードで getElementsByTagName メソッドを呼び出します。DOM には、ノードのコンテンツを取得するために textContent という属性が用意されていますが、残念ながらすべてのブラウザーがこの属性を完全にサポートしているわけではありません。そこで別の手法として、パラグラフから firstChild 属性にアクセスし、この属性の nodeValue プロパティーを取得します。この取得した nodeValue に、ページ内の textarea タグのコンテンツを設定します。textarea のコンテンツは、getElementById メソッドを使って textarea DOM 要素の value プロパティーから取得します。複製したメモに、ユーザーが入力したテキストを注入する方法をリスト 9 に示します (2 番目の目標)。

リスト 9. 複製したメモに、テキスト域のテキストを注入する
addNote : function(event) {
  ...
 // Set the content of the node
 newNode.getElementsByTagName("p")[0].firstChild.nodeValue =  
 document.getElementById("contentArea").value;
        
 notes.appendChild(newNode);
}

3 番目の目標を達成するには、appendChild メソッドを使用して、新しく作成されたメモをメモのリストに追加します (リスト 10 を参照)。

リスト 10. リストに新規メモを追加する
addNote : function(event) {
  ...

 notes.appendChild(newNode);
}

最後に、click イベントに対してデフォルトの振る舞いが行われないようにしなければなりません (アンカーの場合、デフォルトの振る舞いでは、href 属性に指定された URL にユーザーがリダイレクトされます)。DOM でこのタスクを達成する方法として指定しているのは、イベントに preventDefault() を適用し、ハンドラー関数にパラメーターを渡すことです。しかし、このメソッドにしても、バージョン 9 より前の IE ではサポートされていません。バージョン 9 より前の IE で同じ目標を達成するには、event.returnValue 属性を false に設定するという方法を使えます。リスト 11 にデフォルトの振る舞いを阻止するためのコードを記載します。

リスト 11. click イベントに対するデフォルトの振る舞いを阻止する
addNote : function(event) {
  ...

 event.preventDefault ? event.preventDefault() : event.returnValue = false;
}

リスト 12 に、script.js ファイルに含まれる JavaScript コードをすべて記載します。

リスト 12. Script.js
window.SA = {
    
addEvent : function(element, evType, fn, useCapture) { 
        if (element.addEventListener) { 
            element.addEventListener (evType, fn, useCapture); 
            return true; 
        } else if (element.attachEvent) { 
            var r = element.attachEvent('on' + evType, fn); 
            return r; 
        } else { 
            element['on' + evType] = fn; 
        } 
    },
    
    load : function() {
        
        var anchorSelected;
        
        if (document.getElementsByClassName) {
anchorSelected =  document.getElementsByClassName("add")[0];

        } else {
            var anchors = document.getElementsByTagName("a"),
                alenght = anchors.length;
        
            for (var i = 0; i < alenght; i++ ) {
                var anchor = anchors[i];
            
                if (anchor.className === "add") {
                    anchorSelected = anchor;
                }
            }
        }
        
        SA.addEvent(anchorSelected, "click", SA.addNote, false);
    },
    
    addNote : function(event) {
        
        var notes = document.getElementById("notes");
        
        // Clone the node
        var newNode = notes.children[0].cloneNode(true);
            
        // Set the content of the node
        newNode.getElementsByTagName("p")[0].firstChild.nodeValue     
= document.getElementById("contentArea").value;
        
        notes.appendChild(newNode);
        
event.preventDefault ? event.preventDefault() : event.returnValue = false;
    }
}

SA.addEvent(window, "load", SA.load, false);

JavaScript ライブラリーと DOM

開発者が JavaScript コードを作成するときには、JavaScript ライブラリー (つまりフレームワーク) を利用して、ブラウザーによって異なる DOM の実装を処理することがよくあります。例えば、よく使用されている jQuery ライブラリーを使って作成し直したリスト 13 を見てください。

リスト 13. Script-jquery.js
$(function(){
    $('a.add').click(function(){
        var newNote = $('.note').eq(0).clone();
        newNote.find('p').text($('#contentArea').val());
        $('#notes').append(newNote);
        return false;
    });
});

このように、行数がかなり減って、コードが簡潔になります。

jQuery を使用するには、このライブラリーを HTML にインポートする必要があるため (リスト 14 を参照)、HTTP リクエストを 1 つ追加で実行することになり、ライブラリーを実行するための時間が余計に必要になります。このプロセスによってアプリケーションの動作が遅くなる可能性があるので、ライブラリーを使用して、どれだけ作成するコードを削減するかは、開発者の判断次第です。

リスト 14. HTML へのライブラリーのインポート
<html>
    <head>
        ...
        <script src="http://ajax.googleapis.com/ajax/libs/jquery
/1.6.1/jquery.min.js"></script>
    </head>

JavaScript ライブラリーは、開発者の作業を楽にする非常に強力なツールです。ただし、ライブラリーを使うことが必ずしも最も効率的な DOM の処理方法になるとは限らないため、DOM スクリプティングについての知識を持っていなければなりません。また、ライブラリーの背後で何が行われているかについても学んでおくことをお勧めします。


まとめ

DOM は、JavaScript を使って Web ページにアクセスする手段となるため、Web 開発者にとって重要です。ブラウザー・ベンダーによる DOM API の実装には、いくつかの問題と制約事項があり、属性およびメソッドの一部 (addEventListener()textContent など) は、すべてのブラウザーで完全にサポートされているわけではありません。また、ブラウザー間で振る舞いが共通していない場合もあります。

DOM スクリプティングでは、パフォーマンスも重要な考慮事項です。この記事で具体的に説明したように、いくつかの JavaScript フレームワークを利用して DOM を操作およびトラバースすることができますが、それには JavaScript と DOM がやりとりする方法を知っておかなければなりません。


ダウンロード

内容ファイル名サイズ
Article source codeDomScripting.zip5KB

参考文献

学ぶために

製品や技術を入手するために

  • IBM のソフトウェアを無料で試してみてください。試用版をダウンロードすることも、オンライン評価版にログインすることも、Sandbox 環境で製品を操作することも、クラウドを介して IBM 製品にアクセスすることもできます。100 を超える IBM 製品の評価版のなかから選ぶことができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=751086
ArticleTitle=JavaScript を使って DOM をトラバースする
publish-date=08192011