|
Michael Wallent
Microsoft Corporation
December 6, 1999
日本語版最終更新日 2000年2月14日
この記事は、もともと MSDN Online Voices のコラム "DHTML Dude" に掲載されたものです。
今月の福袋に入る前に、私が見た中で最も面白いサイトをみなさんにご紹介したいと思います。http://www.halfbrain.com/。このサイトは Dynamic HTML (DHTML) を使用して、非常にリッチで訴求力のある表計算アプリケーションを作成しています。ドラッグ選択や Excel 形式の式の自動入力など、主要な機能を備えています。驚嘆に値するできばえです。
ダイナミックなテーブル
先月は、階層的なデータ バインディングと、それを使用して XML で情報を表示および更新する方法を説明しました。同様の階層バインディング モデルのアプリケーションを適用して、ツリー リストを実装することができます。しかし、ツリー リスト ビューの一般的な問題の 1 つは、そのパフォーマンスです。大きなリスト (数千個の要素) をロードして操作するのは、時間がかかります。私はこのような場合も、やはりデータ バインディングを使いたいですが、この制約を回避するために、最初のパスでテーブル構造全体をロードするということだけ避けたいのです。
典型的な回避策は、手動でリストを作成して、ノード リストが展開されるときだけ、子ノードのセットを作成することです。しかし、このモデルを使用する場合は、手動で更新を管理する必要があります。今回は手動リストのパフォーマンスと制御、データ バインディングの自動更新とテンプレートという、両方の世界の利点を手に入れてみたいと思います。
この例を見てください。
私が見つけた解決策は、ネストされたデータ バインディング テンプレートを作成することですが、表示されるときだけ、テーブルの dataSrc プロパティを設定します。この方法では、最初にそれぞれのノードについて若干のオーバーヘッドが必要になりますが、非常にダイナミックであるという利点が得られます。
以下に、テーブル テンプレートのコードを示します。
<table id=PrimaryTable cellspacing=0 cellpadding=0 datasrc=#TheData border=0>
<tr onclick="toggle(this)">
<td>
<img id=Icon datafld="image">
<span datafld="value"></span>
<span id=HasChildren style="display: none" datafld="haschildren"></span>
</td></tr>
<tr style="display: none">
<td>
<!-- second level -->
<table cellspacing=0 cellpadding=0 datafld="node" border=0>
<tr onclick="toggle(this)">
<td>
<img id=Icon datafld="image">
<span datafld="value"></span>
<span id=HasChildren style="display: none" datafld="haschildren">
</span>
</td>
</tr>
<tr style="display: none">
<td>
<!-- third level -->
<table cellspacing=0 cellpadding=0 datafld="node.node" border=0>
<tr>
<td>
<img id=Icon datafld="image">
<span datafld="value"></span>
<span id=HasChildren style="display: none" datafld="haschildren">
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
これは非常に基本的な 3 レベルの階層です。通常との違いは、2 つのネストされたテーブルについて、dataSrc プロパティがまだ設定されていないということです。ノードがクリックされたとき、そのノードに子がある場合には、内側のテーブルがその時点でバインドされます。以下に、そのためのコードを示します。
function toggle(e) {
var nextRow;
nextRow = e.nextSibling;
hc = e.all.HasChildren;
if (!hc)
return;
if (hc.innerText == "false")
return;
if (nextRow.style.display == "none") {
nextRow.style.display = "";
e.all.Icon.src = "ofolder.gif";
// skips the td and comment, getting the third child,
// which is the table
nextTable = nextRow.all[2];
if (!nextTable.dataSrc) {
nextTable.dataSrc = "#TheData";
}
} else {
nextRow.style.display = "none";
e.all.Icon.src = "folder.gif";
}
}
フォルダがクリックされると、そのフォルダはそれに関連付けられている <TD> 要素を取得します。ノードが子を持つように指定されている場合は、その下のテーブルが表示されます。テーブルがまだ dataSrc にバインドされていない場合は、dataSrc にフックアップされます。
この変更がパフォーマンスに与える効果は、かなりのものです。標準的なデータ バインディング モデルで 200 ノードのツリーをロードするには、約 4 秒かかります。同じツリーをこのインクリメンタル ロード モデルでロードするには、約 0.5 秒しかかかりません。また、基盤となる XML データが変更された場合、変更はツリーにただちに反映されます。このタイプのインクリメンタルなデータ バインディングを利用することによって、両方の世界の利点が得られます。
コレクション
私が最近よく使用している DHTML 機能の 1 つは、ツリー内のドキュメント オブジェクトおよびその他の要素のコレクションサポートです。
要素への最も一般的なアクセス方法は、その ID を参照するという単純なものです。
<div id=Foo>
</div>
<script>
alert(Foo.tagName);
</script>
私が要素の ID を直接参照した点に注目してください。document.all.Foo 命令は使用していません。document.all.Foo コマンドは、コードの量を増やすだけでなく、読みにくく、処理を遅くします。ドキュメント オブジェクトとコレクションへの追加のアクセスを必要とするためです。ID の直接参照の方が高速でコンパクトです。以前に述べたように、同じオブジェクトに複数回アクセスする場合には、ローカル変数に参照をキャッシュして、その変数を使用して要素にアクセスすれば、さらに高速になります。
<div id=Foo>
</div>
<script>
var a, b, i;
a = Foo;
for (i=0; i<1000; i++) {
b = a.tagName;
}
</script>
ページに同じ ID を持つ複数の要素がある場合、ID の参照はコレクションを返します。
次の例では、アラートは "2" を示します。
<div id=Foo>
</div>
<div id=Foo>
</div>
<script>
alert(Foo.length);
</script>
今までに、みなさんはおそらく document.all コレクションを見慣れたことでしょう。それぞれの要素は、all コレクションと children コレクションの両方を持ちます。all コレクションは要素の子孫 (descendent) 要素のすべてを含んでいるのに対して、children コレクションは直接の子孫だけを含みます。
次の例では、最初のアラートは "2" を示し、2 番目のアラートは "1" を示します。
<div id=Foo>
<b>Child <u>Grandchild</u></b>
</div>
<script>
alert(Foo.all.length);
alert(Foo.children.length);
</script>
tags() メソッドは、どんなコレクションにでも適用できる非常に便利なメソッドです。含まれるアイテムのタグ名によって、どんなコレクションでも容易にサブセットに分けることができ、HTML タグと XML タグの両方に使用することができます。ただし、XML タグとしては、tags() メソッドはタグの名前空間を利用せず、ベース タグ名だけを利用します。
<html xmlns:v>
<body>
<div id=Foo>
<b>Child <u>Grandchild</u></b>
<v:myTag>Custom Tag</v:myTag>
</div>
<script>
var bTags, uTags, mTags;
bTags = Foo.all.tags("B");
uTags = Foo.all.tags("U");
mTags = Foo.all.tags("myTag");
alert(bTags.length);
alert(uTags.length);
alert(mTags.length);
</script>
</body>
</html>
このサンプルを実行すると、3 つのアラートが表示されて、それぞれ "1" を示します。HTML タグでは、タグ名はすべて大文字であることに注目してください。XML タグでは、タグ名は大文字/小文字が区別されるので、タグそのものの指定に正確に一致しなければなりません。この例では、大文字/小文字が混在する例 "myTag" があります。
ここで、このための例を考えてみましょう。私は 100 行のテーブルを作成したいのですが、それぞれの行にユニークなデータを挿入したいと思います。createElement() メソッドを使用して行中の各要素を作成することもできますが、複雑な行の場合は、そのたびに 20 または 30 個の要素を作成することになるかもしれません。これでは、DHTML のメリットは失われてしまいます。代わりに、私は単純なテンプレート モデルを作成しました。まず、テーブルを作成します。
<table>
<tbody id=TheBody>
<tr id=TemplateRow style="display: none">
<td id=CellOne></td>
</tr>
</tbody>
</table>
この行はテンプレートの役目を果たし、カスケーディング スタイル シート (CSS) のプロパティである display: none を使用することによって、これらは画面には表示されません。それぞれの行を作成するために cloneNode() メソッドを使用して、複製した行をテーブルに挿入します。新しい行をテーブルに挿入する前に、正しい行を示すように <TD> のテキストを変更する必要があります。これで、この構造体を調べるだけで、<TD> が <TR> の最初の子であることがわかります。しかし、これは少し不完全な方法です。行番号の前に <TD> を置きたいと思った場合、このアルゴリズムではダメになります。しかし、ターゲット <TD> に既知の ID を与えて、<TR> の all コレクションを使用して、その要素を正確に探し出すことができます。これによって、後で <TR> に追加されるかもしれない他の内容に悪影響を与えるおそれはありません。
以下に、テーブル作成コードを示します。
<script>
function init() {
var i, r, b, d;
b = TheBody;
for (i=0; i<100; i++) {
r = TemplateRow.cloneNode(true);
r.id = "NewRow";
r.style.display = "";
r.all.CellOne.innerText = i;
b.insertBefore(r, null);
}
}
window.onload = init;
</script>
それぞれの行を複製した後、次のような行でセルのテキストを設定します。
r.all.CellOne.innerText = i;
私は <TR> のコレクションを使用して、文書内の全要素をサブセットに分類します(これが実行されるときまでに、"CellOne" という ID を持つノードが非常に多くなっているかもしれないので)。
Internet Explorer でのコレクションサポートは、ページの構造と内容を徹底的に掘り下げるパワフルな手段となります。単純な方法を積み重ねることによって、かなり印象的でパワフルなアプリケーションを作成することができます (スプレッドシートさえ作成できます)。
楽しい休暇を。そして、二度と Y2K という言葉を聞くことがありませんように。
|