tr要素の「display : block;」にはまる

Webページの一部を折りたたんで表示する際に、JavaScriptで要素のdisplayプロパティを書き換えることがよく行われます。
divなどのブロックレベル要素の場合、「要素.style.display = 'none';」でその要素が非表示になり、「要素.style.display = 'block';」で表示されます。

ところが、この手法を使って、表のある行(tr要素)の表示/非表示を切り替えようとしたところ、はまってしまいました。
Internet Explorerでは希望通りの動作になりましたが、その他のブラウザでは表が崩れてしまいました。

この動作の例と、解決方法を紹介します。

1.動作の例

まず、以下の表で、その下にある「切り替え」のボタンをクリックしてみてください。

a1b1c1
a2b2c2
a3b3c3

Internet Explorerでは、ボタンをクリックするたびに、2行目の表示/非表示が切り替わります。
しかし、Firefox等では表が乱れ、2行目の左端のセルに、2行目の内容がすべて詰められて表示されます。
また、Firefoxでは、2行目を再表示するたびに表が長くなっていくという現象も起こります。

ちなみに、表示/非表示の切り替えは、以下のようなJavaScriptで行っています。

function toggle_row_a(id)
{
    var obj = document.getElementById(id);
    obj.style.display =
        (obj.style.display == 'none')
        ? 'block' : 'none';
    return false;
}

2.問題の原因

この現象は、Internet Explorerの不具合(または実装が不十分)ではないかと思います。

「display : block;」は、CSS level 2では、それを指定した要素をブロックレベル要素にするという意味合いを持ちます。
一方、HTML 4.0やXHTML 1.0等では、tr要素はブロックレベル要素には含まれていません。
そのため、tr要素のdisplayプロパティを「block」にした場合、HTMLとCSS level 2の定義に忠実に従うなら、表の行として表示せずに、ブロックレベル要素として表示するのが正しいでしょう。
Firefox等はその通りの動作をしていることになります。

ちなみに、Firefox等でtr要素を表の行として振舞わせるには、displayプロパティに「table-row;」を代入します。
一方、Internet Explorerは「table-row」には対応しておらず、tr要素でも「display : block;」とすることで表の行になります。

3.displayプロパティの値を空にして解決

上記の話から、tr要素の表示/非表示を切り替えるには、Internet Explorerと他のブラウザとで処理を分けなければならないように見えます。
ところが、この問題を解決する方法を探して見たところ、prototype.jsの中に、もっと簡単に解決する方法がありました。

prototype.jsの中に「Element」というクラスがあり、HTMLの要素を操作するメソッドがいくつか用意されています。
このクラスに「show」というメソッドがあり、それを使うと、非表示になっている要素を表示することができます。
このメソッドを見ると、以下のように、displayプロパティの値を空にするという処理が行われています。

$(element).style.display = '';

これにならって、tr要素のdisplayプロパティの値を「none」と空文字とで切り替えるようにしたところ、希望通りの動作になりました(例は以下の通り)。

a1b1c1
a2b2c2
a3b3c3

また、書き換え後のJavaScriptは以下のようになります。

function toggle_row_b(id)
{
    var obj = document.getElementById(id);
    obj.style.display =
        (obj.style.display == 'none')
        ? '' : 'none';
    return false;
}

tr要素はブロックレベル要素と似た表示になりますが、ここまでで書いたように、ブロックレベル要素として扱うと、正しい動作になりません。
意外な落とし穴になりそうなので、覚書としてエントリーしました。