DHTML によるテーブル操作

Michael Wallent
Microsoft Corporation

May 3, 1999
日本語版最終更新日 1999 年 7 月 19 日

この記事は、MSDN Online Voices コラム"DHTML Dude" として掲載されたものです。

Soylent Green Non-MS link を覚えていますか?

「Soylent green は人間から作られているんだ! みんなに知らせないと!」

Web もこれと似たようなものです。ただし、Web は人間からではなく、テーブルから作られています。そう、テーブルから作られているのです。あなたが毎日見ているサイトのソースコードを見て、<TABLE>を検索すると、これが(いくつもではないにせよ)少なくとも1つは含まれているのがわかるでしょう。

残念なことに、Internet Explorer 4.0 の時代には、DHTML を使ってテーブルを操作しようとすると……。いや、正直に、操作は不可能だったと言っておきましょう。私が気に入っていた機能の1つ、display プロパティを使用すると、表示を閉じたときに枠の小さなゴミが残りました。行やセルの追加や削除を試みたことはありますか? 10号ビルの2人のテスターは、これを行う魔術的な方法を考えついたようですが、いずれにしてもそれは怪しげで複雑なアートです(また、かなり汚いプロセスでもあります)。

あまりにも難しく、あまりにも複雑で、うまく行きませんでした。すっぱり忘れましょう。

では、よいお知らせです。Internet Explorer 5 では、display プロパティはテーブルの行とセルに対して機能します。また、テーブルのセルと行の挿入と削除も簡単に行えます。また、これを移動するのもかなり簡単です。

サンプルをご覧になってください。

選択

このテーブル エディタでは、ユーザーはクリックを行うことで1つのセルを選択することができます。また、ALTキーを押しながらクリックを行うことで、行全体を選択することもできます。それでは、これがどのように実現されているかを見ていきましょう。

function select(element) {
  var e, r, c;
  if (element == null) {
    e = window.event.srcElement;
  } else {
    e = element;
  }
  if ((window.event.altKey) || (e.tagName == "TR")) {
    r = findRow(e);
    if (r != null) {
      if (lastSelection != null) {
        deselectRowOrCell(lastSelection);
      }
      selectRowOrCell(r);
      lastSelection = r;
    }
  } else {
    c = findCell(e);
    if (c != null) {
      if (lastSelection != null) {
        deselectRowOrCell(lastSelection);
      }
      selectRowOrCell(c);
      lastSelection = c;
    }
  }

  window.event.cancelBubble = true;
} 

TableContainer.onclick = select;

function cancelSelect() {

  if (window.event.srcElement.tagName != "BODY") 
    return;

  if (lastSelection != null) {
    deselectRowOrCell(lastSelection);
    lastSelection = null;
  }
}

document.onclick = cancelSelect;

function findRow(e) {
  if (e.tagName == "TR") {
    return e;
  } else if (e.tagName == "BODY") {
    return null;
  } else {
    return findRow(e.parentElement);
  }
}

function findCell(e) {
  if (e.tagName == "TD") {
    return e;
  } else if (e.tagName == "BODY") {
    return null;
  } else {
    return findCell(e.parentElement);
  }
}

function deselectRowOrCell(r) {
  r.runtimeStyle.backgroundColor = "";
  r.runtimeStyle.color = "";
}

function selectRowOrCell(r) {
  r.runtimeStyle.backgroundColor = "darkblue";
  r.runtimeStyle.color = "white";
}

テーブルに対するすべてのクリックイベントを取得するために、TableContainer.onclick イベントを select() メソッドに関連付けています。ただし、実際には、この select() メソッドはコード内の他の場所でも使用しています。たとえば、新しい行が選択されたときには、その行が挿入された後に選択を行うようにします。このために、select() メソッドは、選択する要素を選ぶためのオプションのパラメータを取ります。

select() メソッドの他のコードのほとんどは、一度に1つの行またはセルだけが強調表示されるようにする選択/選択解除のロジックです。

click ハンドラの srcElement が、必ずしも実際のテーブルのセル(<TD>)であるとは仮定されていないことに注意してください。ユーザーがセルの中のテキストをクリックすると、srcElementは<TD>になります。しかし、<TD><B>Some Bold</B> Text</TD> のような、より複雑な HTML 構造の場合、"Some Bold" をクリックすると "Text" をクリックした場合とは別の srcElement が返されます。このために、findCell/findRow メソッドを再帰的に使用して、それぞれのクリックイベントが発生した <TR> または <TD> を探すために、一連の parentElement を再帰的に検索します。

選択プロセスは Internet Explorer 5 のいくつかの新しい機能も使用します。セルまたは行が「選択」されている場合には、視覚的に異なる表示になるべきです。これは、カスケーディング スタイル シート(CSS)の color および background-color プロパティを設定することで簡単に実現できます。ただし、element.style.color を設定すると、選択プロセスはユーザーが(Set Row/Cell Style関数を使って)設定した CSS プロパティをすべて上書きすることになります。ユーザーがセルまたは行の CSS プロパティを設定できるようにし、またプログラムを通して行った色の操作がそれに干渉しないようにするために、runtime style と呼ばれるオブジェクトを使用します。

runtime style オブジェクト(element.runtimeStyle)を通して設定されたスタイルは、他の全ての CSS プロパティ設定の値を上書きします。runtimeStyle オブジェクトは別に保持されているので、これを変更しても他の CSS 設定には干渉しません。たとえば、次のシーケンスを考えます。

element.style.color = "blue"; // element is now blue
…
element.runtimeStyle.color = "red"; // element is now red
…
element.runtimeStyle.color = ""; // element is blue again

runtimeStyle に加えた変更が、要素の他の CSS 設定にどのように上書きするか注目してください。ただし、オブジェクトが削除されると、最初の CSS 値が再適用されます。

セルの追加

HTML または XML 要素の追加は、Internet Explorer 5 で大幅に簡単になっています。Internet Explorer 4.0 では、要素を追加するためには、ページに HTML によって記述したその要素の表現を挿入する必要がありました。


document.body.insertAdjacentHTML("AfterBegin", "<img src='ie.gif'>");

一部の要素は、この方法では作成することもできませんでした。テーブルの要素とフレームセット、および文書のヘッダーの中の要素は、簡単には操作できませんでした。

Internet Explorer 5 では、これらの要素の作成は単純明快になっています。HTML 構文を理解していなくても、ドキュメントに新しい要素を挿入することができます。


i = document.createElement("IMG");
i.src = "ie.gif";
document.body.insertBefore(i, null);

次に、テーブル行に新しいセルを追加するコードを示します:


function addCell() {
  var r, p, c, nc, text;
  if (lastSelection == null)
    return false;

  r = lastSelection;

  if (r.tagName == "TD") {
    r = r.parentElement;
    c = lastSelection;
  } else {
    c = null;
  }

  nc = document.createElement("TD");
  text = document.createTextNode("New Cell");

  nc.insertBefore(text, null);
  r.insertBefore(nc, c);

  select(nc);

  return nc;
}

このメソッドの最初の部分は、単に現在の選択を探しているだけです。しかし、最後の5行が重要な役割を果たしています。新しい <TD> は createElement() メソッドを使って作成されます。この方法で作成された要素は、対象文書の中で任意の要素の子要素として追加されるまで、文書内には表示されないということを理解しなくてはなりません。文書の外部で、要素の木構造を作成することが可能です。(insertBefore() は、メインツリーではなく、要素に対して呼び出します)。この方法を使うと、文書のセクション全体を一度に追加(または削除)することができます。

この例では、新しいセルに何らかのテキストを追加します。要素が主要な文書ツリーの外部にあるときに、テキストの作成または操作を行うには、テキストノードを使用する必要があります。テキストノードはテキストだけを含んでおり、他の HTML 要素は含んでいません。<TD>に、テキストを含んでいる bold タグを入れるためには、<B>要素を作成し、この<B>を<TD>に挿入し、テキストを<B>に挿入する必要があります。テキスト ノード オブジェクトの詳細については、Web Workshop の DHTML Objects エリアを参照してください。

前のセクションで説明した select() メソッドを使って、新しく作成したテーブルのセルを選択していることに注意してください。

行の除去

多くの場合でそうですが、作成するよりも破棄する方が簡単です。次にテーブル行を除去するためのコードを示します。

function removeRow() {
  var r, p, nr;
  if (lastSelection == null)
    return false;

  r = lastSelection;

  if (r.tagName == "TD") {
    r = r.parentElement;
  }

  p = r.parentElement;

  p.removeChild(r);

  lastSelection = null;
 
  return r; 
}

1つのメソッド、removeChild() を使って、任意の要素、または要素のサブツリーを文書から除去することができます。この方法で要素をツリーから除去しても、要素が削除されるわけではないことに注意してください。除去された要素は、新規作成された要素と同じ状態にあります。これらの要素はいつでも文書内の別の位置に再挿入することができます。スクリプトの観点から見ると、これらの要素対する参照がなくなった時点で実際に削除されます。

要素の移動

これまでに、insertElement() メソッドを使って、新しい要素を既存の文書に配置する方法を説明しました。このメソッドは、要素を再配置するためにも使用できます。すでにツリーの中に存在する要素に対して insertBefore() を呼び出すことが可能です。これにより、要素は現在の位置から除去され、新たに特定の位置へ自動的に移動されます。これは、removeChild() の後に insertBefore() を呼び出すのと似ています(ただしこちらの方が短くなります)。

次に、テーブルのセルを左に1列移動するコードを示します。

function moveLeft() {
  var c, p, ls;
  if (lastSelection == null)
    return false;

  c = lastSelection;

  if (c.tagName != "TD") {
    return null;
  }

  ls = c.previousSibling;

  if (ls == null)
    return null;

  p = ls.parentElement;

  p.insertBefore(c, ls);

  return c;
}

セルを左に移動するには、単に、すぐ左の隣接セルの前にセルを挿入します。隣接セルへのナビゲーションには、2つの新しいプロパティ、previousSibling と nextSibling を使用します。moveRight() メソッドも良く似ていますが、代わりに nextSibling プロパティを使用していることに注意してください。

練習問題として、テーブルの行でも使用できる moveLeft() を作成してみてください。moveLeft()moveUp() に共通のコードメソッドはありますか?

内容と書式の編集

私が初めて Lotus 1-2-3 を見たときに、最も感銘を受けた機能の1つが、セルの中の値が入力と同時にすぐさま更新されたことでした(1990年にはそうだったのです)。この機能は今日でも、かなり実現が難しいものです。

しかし、Internet Explorer 5 の新しい機能のおかげで、この処理はきわめて簡単になります。ダイナミックプロパティを使うと、たったの3行でできてしまいます。

function editContents() {
  var c, p, nr;
  if (lastSelection == null)
    return false;

  c = lastSelection;

  if (c.tagName != "TD") {
    return null;
  }

  EditCell.style.display = "";

  EditCell.value = c.innerHTML;

  c.setExpression("innerHTML", "EditCell.value");

  EditCell.focus();

  EditCell.onblur = unhookContentsExpression;
}

function unhookContentsExpression() {
  lastSelection.removeExpression("innerHTML");
  EditCell.value = '';
  EditCell.style.display = "none";
}

ダイナミックプロパティを使う場合には、セルの innerHTML プロパティを入力の値として定義します。入力の値が変化すると、セルの内容も自動的に変化します。監視しなくてはならないイベントは onblur イベントだけなので、この関係を利用することができるのです。キーストロークを監視する必要はありません。

ボタンの有効化

さまざまなタイプの要素を選択すると、それに応じてページ上のボタンのオン/オフが切り替わることに気づきましたか? この種の動作は、通常はアイドルループプロセスか、アプリケーションの中で状態の変化が起こるすべての場所を追跡して、それに応じてオン/オフを決めることによって実現されます。なんと面倒なことでしょう。しかし、ダイナミックプロパティを使うと、これが簡単に実現できます。

ButtonAddRow.setExpression("disabled", "nothingSelected(lastSelection)");

ButtonMoveRight.setExpression("disabled",
"! cellSelected(lastSelection)");

ButtonEditContents.setExpression("disabled",
"(! cellSelected(lastSelection))||(EditCell.style.display == '')");

ButtonEditStyle.setExpression("disabled", 
"(EditStyle.style.display == '')");

ButtonEditStyle.setExpression("value",
"'Edit ' + whatIsSelected(lastSelection) + ' Style'");

ここでは、nothingSelected()cellSelected()、およびrowSelected() という3つの補助メソッドを作成しました。各ボタンの disabled プロパティがダイナミックプロパティとして設定されているので、式はボタンを無効にすべきときに true となります。たとえば、"Move Right" はセルに対してしか作用しないので、セルが選択されていないときには、このボタンはオフでなければなりません。

lastSelection プロパティが、これらすべてのメソッドへのパラメータとして送られていることに注意してください。このプロパティは式の中で参照されているので、依存関係が生じています。そのプロパティが変化するたびに、式の再評価が行われます。コード内のどの場所でどのように lastSelection が変化した場合でも、すべてのボタンが追加のコードやオーバーヘッドなしに自動的にオン/オフになります。

要約

上の例が、HTMLテーブルを扱う上での参考になることを願っています。では来月またお会いしましょう。

Michael Wallent は、Internet Explorer 担当のグループ プログラム マネージャです。


表示: