技術的な解説(Technical Description)

DOMとJavaScript

DOMとは、w3cによって策定された、プログラムからxml(html)ドキュメントに、アクセスするためのインターフェイスです。このインターフェイスは、特定のベンダーに依存することはありません。また、プログラム言語から、独立した形で仕様が決められているため、どのような言語にでも実装できるようになっています。

DOMが、プログラム言語からは、独立した形で仕様が決められていると書きましたが、DOMの仕様には、JavaとECMAScriptのAPIが、付録という形で載っています(ECMAScriptとは、JavaScriptの現時点の標準仕様のことで、一般的なWebブラウザはこの仕様に則って、JavaScriptを実装しています)。このあたりが浸透すれば、JavaScriptでも、互換性の不安無く、DOMを使える環境が整いつつあります(いつかはきっと・・・・)。

DOMの守備範囲は、あくまでもxml(html)へのアクセスです。タグの表示スタイルを変更したり、特定のIDが設定されているタグを取得したりすることは、仕様の範囲内ですが、新しいウィンドウを開いたり、マウスカーソルを移動したりといった、xml(html)ドキュメントへのアクセス以外のことは、仕様の範囲外となります。

JavaScriptでDOM

JavaScriptでDOMを使う場合、何はともあれdocmentオブジェクトを取得する必要があります。但し、DOMの仕様では、docmentオブジェクトを取得する方法は、定められてはいません。これは各実装者(例えば、Webブラウザの作成者)が、勝手に決めることを意味しています。幸いにも、htmlの中に記述またはリンクされたJavaScriptでは、docmentオブジェクトは、すでに用意されているので、特に取得する必要はありません。

以下の例では、ドキュメントに、"Hello World"と表示します。

JavaScript
document.writeln('Hello World');

見つける、ほしい物を

DOMで何かをする場合、対象となる要素を見つけなければなりません。このためのメソッドが、docmentオブジェクトにはいくつか用意されています。代表的な物を以下にあげます。

IDL
Element  getElementById(in DOMString elementId);
NodeList getElementsByName(in DOMString elementName);
NodeList getElementsByTagName(in DOMString tagname);

getElementByIdは、ドキュメント内から、特定の id が設定してあるエレメントを探します。html4 の仕様上、id はドキュメント内でユニークであることから、このメソッドは、単一のエレメントを返します。

getElementsByNameは、ドキュメント内から、特定の name が設定してあるエレメントを探します。html4 の仕様上、複数のタグに同一の name が設定できることから、このメソッドは、複数のエレメントを含むリスト(配列)を返します。

getElementsByTagNameは、ドキュメント内から、特定の名前のタグ探します。このメソッドは、複数のエレメントを含むリスト(配列)を返します。

HTML
<input type="text" name="column0" size="20" id="row0column0">
<input type="text" name="column1" size="3"  id="row0column1">
<input type="text" name="column2" size="10" id="row0column2"><br>
<input type="text" name="column0" size="20" id="row1column0">
<input type="text" name="column1" size="3"  id="row1column1">
<input type="text" name="column2" size="10" id="row1column2"><br>
<input type="text" name="column0" size="20" id="row2column0">
<input type="text" name="column1" size="3"  id="row2column1">
<input type="text" name="column2" size="10" id="row2column2"><br>
<input type="text" name="column0" size="20" id="row3column0">
<input type="text" name="column1" size="3"  id="row3column1">
<input type="text" name="column2" size="10" id="row3column2"><br>

上記のようなhtmlドキュメントに対し、

JavaScript
var item = document.getElementById('row1column2');

を実行すると、itemには、2行目3列目のinputエレメントが格納されます。

同様に、

JavaScript
var items = document.getElementsByName('column1');

を実行すると、itemsには、2列目のinputエレメントが4つ格納されます。

JavaScript
var items = document.getElementsByTagName('input');

の場合は、itemsには、全てのinputエレメントが格納されます。

スタイルが気になる?

エレメントさえ見つけてしまえば、後は好きなように料理できます。サイズ・色・表示/非表示なども、簡単に変更できます。そう、スタイルを変えてしまえば良いのです。

下記の例では、name というIDを持ったエレメントの背景を、赤に変更します。

JavaScript
document.getElementById('name').style.backgroundColor = 'red';

エレメントオブジェクトのstyle属性は、"DOM Level 2 Style Specification" で定義された、CSSStyleDeclarationオブジェクトです。アプリケーションからは、このオブジェクトを通して、各エレメントの、インラインスタイルにアクセスすることができます。

CSSStyleDeclarationオブジェクトからアクセスできるスタイルは、CSS2Propertiesに定義されている項目のうち、該当エレメントに設定可能なものだけです。スタイルを設定できないエレメント(例えばtitleエレメント)のスタイルを変更したり、CSS2Propertiesに定義されていない項目を変更しようとすると、エラーになります。

ご参考までに、CSS2PropertiesのIDL定義(w3cのサイトへのリンクです。)

チマチマやってられないときは

上記の方法は、個々のタグに対して、スタイルを変更するものでした。しかし、状況によっては、複数のタグの内容を、一度に変更したい場合が有ります。

このような場合、スタイルシートのセレクタ毎に、スタイルの内容を設定することができます。

IDLは、以下のようになります。

IDL:Document Object Model Style Sheets
interface DocumentStyle {
  readonly attribute StyleSheetList styleSheets;
};

インターフェイス、DocumentStyleは、documentオブジェクトに実装されているので、documentオブジェクトから、直接styleSheetsアトリビュートを参照できます。

IDL:Document Object Model Style Sheets
interface StyleSheetList {
  readonly attribute unsigned long length;
  StyleSheet item(in unsigned long index);
};

itemメソッドは、定義されているスタイルシートがCSSの場合、CSSStyleSheetを返します。

IDL:Document Object Model CSS
interface CSSStyleSheet : stylesheets::StyleSheet {
                  -- 省略 --
  readonly attribute CSSRuleList cssRules;
                  -- 省略 --
};

IDL:Document Object Model CSS
interface CSSRuleList {
  readonly attribute unsigned long    length;
  CSSRule item(in unsigned long index);
};

IDL:Document Object Model CSS
interface CSSRule {

  // RuleType
  const unsigned short UNKNOWN_RULE   = 0;
  const unsigned short STYLE_RULE     = 1;
  const unsigned short CHARSET_RULE   = 2;
  const unsigned short IMPORT_RULE    = 3;
  const unsigned short MEDIA_RULE     = 4;
  const unsigned short FONT_FACE_RULE = 5;
  const unsigned short PAGE_RULE      = 6;

  readonly attribute unsigned short type;
           attribute DOMString      cssText;
                     // raises(DOMException) on setting

  readonly attribute CSSStyleSheet parentStyleSheet;
  readonly attribute CSSRule       parentRule;
};

IDL:Document Object Model CSS
interface CSSStyleRule : CSSRule {
  attribute DOMString selectorText;
            // raises(DOMException) on setting

  readonly attribute CSSStyleDeclaration  style;
};

まず、ドキュメントオブジェクトから、スタイルシートのリストを取得します。このリストの中には、ドキュメントに対して、linkエレメントでリンクされたスタイルシートや、stylesheetエレメントで指定されたスタイルシートが含まれます。

次に、スタイルシートの中から、必要なCSSRuleオブジェクトを検索します。このとき、セレクタ名称などで検索する方法は提供されないので、自分で用意することになります。

必要なCSSRuleオブジェクトさえ見つければ、後は特定のタグのスタイルを変更する場合と、大差有りません。但し、直接スタイルを変更できる、CSSStyleDeclarationオブジェクトを持っているのは、RuleTypeSTYLE_RULEFONT_FACE_RULEPAGE_RULEである、CSSRuleオブジェクトだけである点に注意が必要です。

また、同じセレクタに対し、複数のスタイルを設定している場合(カスケードしている場合)、当然同じセレクタ名称が複数見つかる事にも、注意する必要が有るでしょう。

下の例では、日本語の部分に.jp英語の部分に.enをクラスで設定しておき、全ての英語部分を非表示、全ての日本語部分を表示にしています。どのような記述のスタイルシートにも対応させるためには、CSSRuleオブジェクトのselectorTextアトリビュートを判定する前に、RuleTypeSTYLE_RULEPAGE_RULEである事を、チェックしなければなりません。何故なら、それ以外のRuleTypeの場合、selectorTextアトリビュートが存在しないからです。

非常に強力な機能ですが、残念ながらIE6以下では動作しません。

JavaScript
    var styleList = document.styleSheets;
    for (var i = 0; i < styleList.length; i ++) {
        var styleSheet = styleList.item(i);
        var cssRuleList = styleSheet.cssRules;
        for (var j = 0; j < cssRuleList.length; j ++) {
            var cssRuleItem = cssRuleList.item(j);
            /*
             * 初期表示は、日本語
             */
            if (cssRuleItem.selectorText == '.en') {
                cssRuleItem.style.display = 'none';
            }
            else if (cssRuleItem.selectorText == '.jp') {
                cssRuleItem.style.display = '';
            }
        }
    }

サーバとの通信をどうするか

通常Webアプリケーションの、クライアント・サーバ間の通信は、クライアントからのリクエストに対して、サーバが次ページのデータを、全てレスポンスするというのが、一般的です。

同一の画面から、データを次々に入力したいような場合、これはやっかいです。また、キーのみサーバに送って、検索結果を表示したいような場合も、本来なら検索結果だけをレスポンスすれば良いはずですが、実際には表示するページ全体を、レスポンスしています。

JSPを普通に使うと、このような形になってしまうのですが、パフォーマンスの面で問題があることは、明らかです。特に最近のWebアプリケーションの画面が、デザイン的にも凝ったものになってきていることを考えると、この問題は大きくなるかもしれません。

比較的遠い将来を見据えれば、"DOM Level 3 Load and Save"あたりが、解決策になるのかもしれませんが、本当に使える様になるのは2〜3年後でしょうから、まだまだ先の話です。

(注)"DOM Level 3 Load and Save"が、本当に解決策になるのかどうかは、分かりません。そもそも"DOM Level 3 Load and Save"が、何のために策定された(る)仕様なのか、私自身がはっきり知らないのですから、問題外です(笑)。baseURIから、Streamを使ってドキュメントを読み書きする仕様のようなので、「そんなもんかなあ」と想像した訳で・・・・。

当サイトの例では、サーバからのレスポンスを、別フレーム(表示されないbufferフレーム)に読み込み、ロード完了のイベントで、メインとなるフレームの画面に、内容を表示しています。インターネット上でも、それほどストレス無くレスポンスされてくるので、今後のプロジェクトでIE5.5以上のみをターゲットにしたものが有れば、試してみようと思っているものです。

この方法には、もう一つうれしい副作用があります。メインとなる画面を、サーバロジックと完全に切り離した、HTMLだけで作成できると云うことです。通常JSPでは、HTMLにjavaのロジックが多少なりとも記述されるため、それだけで完全な形の表示をすることは難しいのですが、HTMLだけで記述されていれば、そのようなことはありません。

また画面遷移も、HTMLだけで記述されていれば、簡単にクライアントだけで実現できます。まずクライアントをプロトタイプの様に先行し、使用感等を先に確認をしてもらって、その間にサーバの実装をする。できそうでできないこんな事も、簡単にできます(たぶん)。

クライアントの画面と、サーバ側のロジックとの接点は、HTMLの各エレメントに設定されたidのみ。という事が実現できれば、プロジェクトの自由度が(設計もスケジュールも)少しだけ広がる気がします。

まあ、JSPだけで実現するプロジェクトよりは結構大変で、クライアントをアプレットで作るプロジェクトよりは、かなり楽という感じでしょうか。