はじめに
DataGridコントロールはデータを表示するための非常に強力で汎用的なコントロールであり、ASP.NET開発者にもお馴染みである(DataGridの扱いに慣れていない人は、まずScott Mitchellの記事「An Extensive Examination of the DataGrid Control」を読むことをお勧めする)。私はこれまで多くのプロジェクトでDataGridコントロールを使用し、DataGridの見た目をもっと良くしようとしてきた。そんな中の1つで現在Yahoo! MailやHotmailなどのサイトで利用されているのが、マウスでポイントされた行を自動的にハイライト表示するというテクニックだ。つまり、エンドユーザーがDataGridのデータ上にマウスを持ってきたときに、マウスが現在置かれているDataGrid行の背景は他の行とは違う色になるわけだ。
DataGridコントロールを拡張してこの機能を追加する方法については、既に数多くの記事が書かれているので、中にはマウスの動きに応じて行をハイライト表示する方法を既に知っている人も多いかもしれない。この方法の一例が、Scottの「Creating a Row-Selectable DataGrid Control」だ。この記事やその他の多くの記事で取り上げているのが、DataGridを拡張するカスタムASP.NETサーバーコントロールを作成するというソリューションで、カスタムサーバーコントロールから、行をハイライト表示するためのクライアントサイドスクリプトを発行するという形式だ(特に、<tr>
HTML要素にはonmouseover
属性とonmouseout
属性が追加されている)。
カスタムサーバーコントロールを作成する方法は、簡単だし役立つ情報も多い。しかし私は、もっと単純なアプローチで、標準のDataGridコントロールをいっさい変更せずに動作するようなものを作りたいと考えた。これを実現する方法は、AListApartのWebサイトで、CSSだけでドロップダウンメニューを作成する方法についての記事を読んだ後で思いついた。このコンセプトは単純で、「CSSのhoverスタイルを利用して、さまざまな階層形式のリストの表示/非表示を切り替える」というものだった。私はその単純さと斬新なコンセプトに心ひかれ、これに少し手を加えれば、DataGridの行もハイライト表示できるのではないかと思いついた。
私がどのようにクライアントサイドCSSのhover機能を利用して、標準のDataGridに行のハイライト表示機能を追加したかについて説明する。完成するまでの過程でいくつかの問題があったが、その問題と回避方法についても説明する。続きを読む前に、まず実際のデモを見ていただきたい。
はじめの一歩:DataGridにデータを表示する
当然ながら、DataGrid内の行をハイライト表示するためには、操作対象となるDataGridが必要である。以下は、データをDataGridにバインドするサンプルとして作成したページのコードで、上記のデモページは、このページに機能を追加して完成させたものである。
<%@ Page Language = "C#" %> <%@ Import Namespace = "System.Data" %> <html> <head> <title>DataGrid Sample</title> <script runat = "server"> private void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) BindGrid(); } private void BindGrid() { DataTable table = new DataTable("Users"); table.Columns.Add("firstName"); table.Columns.Add("lastName"); table.Columns.Add("emailAddress"); AddRow(table, "Bugs", "Bunny", "bbunny@wb.com"); AddRow(table, "Mickey", "Mouse", "mmouse@disney.com"); AddRow(table, "Donald", "Duck", "dduck@disney.com"); AddRow(table, "Dan", "Marino", "dmarino@dolphins.com"); AddRow(table, "Steve", "Stchur", "sstchur@yahoo.com"); myGrid.DataSource = new DataView(table); myGrid.DataBind(); } private void AddRow(DataTable table, string firstName, string lastName, string email) { DataRow row = table.NewRow(); row["firstName"] = firstName; row["lastName"] = lastName; row["emailAddress"] = email; table.Rows.Add(row); } </script> </head> <body> <asp:DataGrid id = "myGrid" CellPadding = "5" AutoGenerateColumns = "false" Font-Name = "verdana" Font-Size = "10pt" runat = "server"> <HeaderStyle BackColor = "#336699" ForeColor = "#ffffff" Font-Bold = "true" /> <AlternatingItemStyle BackColor = "#eeeeee" /> <Columns> <asp:BoundColumn HeaderText = "First Name" DataField = "firstName" /> <asp:BoundColumn HeaderText = "Last Name" DataField = "lastName" /> <asp:BoundColumn HeaderText = "Email" DataField = "emailAddress" /> </Columns> </asp:DataGrid> </body> </html>
この時点のDataGridは、ハードコーディングされたデータにバインドされた、ごく単純なものだ。実際のデモを見てもわかるとおり、この例ではHeaderStyle
で見出し行の背景色を落ち着いた青に設定し、AlternatingItemStyle
プロパティで行の背景色を1行おきに明るい灰色に設定している。
最初の実験
行を"ハイライト表示可能"にするために、まず単純に、ページ上のすべてのTR
(table row)要素に次のようなCSSスタイルを追加してみた。
<style type = "text/css"> tr:hover { background-color: #ffccff; } </style>
DataGrid内の各行は<tr>
要素で表現されているので、このCSSを適用すれば、ユーザーがDataGridの行をマウスでポイントしたときに行の背景がピンク(#ffccff
)になるだろうと考えたのだ。この方法をまずWebブラウザのFirefoxでテストしたところ、マウスでポイントするとDataGridの行がハイライト表示された。しかし問題は、見出し行も含めた、すべてのDataGridの行がハイライト表示されることだった。また、このCSSをInternet Explorer(IE)でテストすると、まったくハイライト表示されないことがわかった。
Internet Explorerでも動作するように修正
調べた結果、IEはハイパーリンク(<a>
タグ)にのみ:hover
をサポートしていることがわかった。幸いこの問題は、一部のJavaScriptとCSSスタイルシートに少し手を加えるだけで簡単に解決できた。具体的には、tr
のレベルにover
というクラスを定義し、:hover
と同じCSSを適用したのだ。その結果、CSSスタイルシートは次のようになった。
<style type = "text/css"> tr:hover, tr.over { background-color: #ffccff; } </style>
当然ながら、この変更だけでは問題は解決できていない。まだ他に、DataGridの<tr>
要素をover
クラスに関連付けるという作業が残っている。行がマウスでポイントされたときに各<tr>
要素のclass
属性をover
に設定するためには、ちょっとしたJavaScriptを記述する必要がある。
<script language = "javascript"> startHighlight = function() { if (document.all && document.getElementById) { navRoot = document.getElementById(ID of DataGrid); // Get a reference to the TBODY element tbody = navRoot.childNodes[0]; for (i = 0; i < tbody.childNodes.length; i++) { node = tbody.childNodes[i]; if (node.nodeName == "TR") { node.onmouseover=function() { this.className = "over"; } node.onmouseout=function() { this.className = this.className.replace("over", ""); } } } } } window.onload = startHighlight; </script>
上記のスクリプトでは、DataGridのすべての子要素について検索を開始するようブラウザに指示している(注:ID of DataGrid
の部分は、DataGridに基づいて描画される<table>
コントロールのクライアントサイドIDに置き換える必要がある。DataGridがユーザーコントロール、別のDataGrid、またはその他の名前付きコンテナ内に置かれていない場合、このIDはDataGrid WebコントロールのID
プロパティと等しくなる)。このスクリプトは、ノード名がTR
のDataGrid要素を見つけると、その要素にonmouseover
関数とonmouseout
関数を追加する。これらの関数を追加する目的は、単純に<tr>
がマウスでポイントされているかどうかに応じて、そのCSSクラスをover
またはnothingに設定することた。
このコードをIEでテストすると、奇妙な点に気付く。表内の行が1行おきにしかハイライト表示されないのだ。これは、AlternatingItemStyle
によって、行の背景色を1行おきに明るい灰色(#eeeeee
)にするようDataGridに指示しているからである。ASP.NETはこのスタイルをインラインで描画するため、このスタイルの方がJavaScriptで指定したCSSクラスよりも優先される。事実、ItemStyle
を同様に指定した場合には、まったく同じ理由で行が1つもハイライト表示されなかった。
この問題を解決するにはどうしたらいいだろうか。答えは簡単だ。TR
がマウスでポイントされているときに、すべてのTD
要素(TR
の子要素)が、それにアタッチされたCSSクラスover
を受け取るようにすればよい。TD
要素に設定されたスタイルは親(TR
要素)のスタイルよりも優先されるので、この方法で正しく表示される。
修正後のCSSは次のようになる。
<style type = "text/css"> tr:hover, tr.over td { background-color: #ffccff; } </style>
ヘッダー行のハイライト表示の抑制
IE固有の問題はこれで解決した。しかし、まだ見出し行がハイライト表示されてしまう問題が残っている。さてどうしたものか。気付いた方もいるかもしれないが、IE対応のJavaScriptのコードでは、テーブル(DataGrid)内のすべての行を1つずつループ処理している。そこで、次のように行インデックスを0ではなく1から開始して、1行目(ヘッダー行)をスキップすることでこの問題は簡単に解決する。
// In the startHighlight function... for (i = 1; i < tbody.childNodes.length; i++) { // this code stays the same }
このコードをIEでテストすると、ヘッダー行をマウスでポイントしてもハイライト表示されないようになる。また、CSSスタイルシートにtd
を追加したため、IEでもDataGrid内の行が1行おきではなくすべてハイライト表示されるようになっている。
では、Firefox(やその他のIE以外のブラウザ)ではどうしたらよいだろうか。この問題を解決するには、特定のTR
要素にのみhoverスタイルを適用することをブラウザに指示する必要がある。もっとも、CSSでは簡単に実現できる。具体的には次のようになる。
<style type = "text/css"> tr.row:hover, tr.over td { background-color: #ffccff; } </style>
このCSSでは、CSSクラスがrow
であるTR
要素にのみhoverスタイルを適用することをブラウザに指示している。
回避するためには、前述のJavaScriptコードをFireFoxでも動くように書き直すという方法も考えられる。私もその方法を検討してみたが、やはり私はCSSの信奉者なので、CSSの方が優れたアプローチであり"正道"だと思う。JavaScriptは小手先の処理のように感じられてしまうのだ。
というわけで、私は可能な場合にはいつでも"正道"を進み、どうしても必要な場合にのみJavaScriptに頼るようにしている。もちろん、ここで紹介したJavaScriptをFireFoxで動作するように修正してもまったく問題はない。ただ個人的なポリシーとして、ここではCSSのみを使う方向性を貫いた。
ここまででは、DataGrid内のTR
要素にCSSクラスが関連付けられていないので、このままではうまく動作しない。これを修正するには、DataGridのAlternatingItemStyle
セクションとItemStyle
セクションに次のようなCssClass
プロパティを指定する。
<AlternatingItemStyle BackColor = "#eeeeee" CssClass = "row" /> <ItemStyle BackColor = "#ffffff" CssClass = "row" />
まとめ
これで完成である。簡単にDataGrid内の行をハイライト表示することができ、コードの量もこれまでに見たり実装したことのある他の方法よりかなり少ないはずである。ただ1つ断っておきたいのは、私が動作検証したのはIE 6.0とFirefox 1.0のみであるということだ。他のブラウザでどのような動作になるかはわからない(Netscape 7とIE 5.5ではおそらく正常に動作すると思う)。他のブラウザで正常に動作しない場合は、JavaScriptを使うことになる。紹介したコードを修正するだけで、きっと正常に動作するようになるだろう。
最後になったが、本稿のJavaScriptコードは私が作成したものではない。私が一部修正を加えているものの、大部分はAListApart.Comに掲載されたPatrick GriffithsとDan Webbの記事「Suckerfish Dropdown」から借用したものだ。この記事にはCSSについての非常に有意義な内容なので、ぜひお読みになることをお勧めする。
プログラマーに幸あれ!