はじめに
もしあなたがWebアプリケーション開発者ならば、データをテーブルとして表示するための作業を毎日行っていることでしょう。本稿では、DisplayTagライブラリと簡単なJavaScriptを使用して、さまざまな機能を備えたJSPページ用のテーブルを短時間で作成する方法を紹介します。具体的には、次のような機能を持ったテーブルを作成します。
- 1行ごとに異なる背景色
- 列の並べ替えと書式設定
- 行のグループ化と小計の計算
- ページナビゲーション
- XML、Excel、PDF、CSVへのエクスポート
- 標準JSPおよびEL(Expression Language)のサポート
- 行のインタラクティブ操作――ユーザーがいずれかの行をマウスポインタで指すと、その行が強調表示される。ユーザーが行のどこかをクリックすると、その行についての識別情報を含んだ新しい要求が生成される。
本稿では、いくつもの注文の明細行を表示するDisplayTagExというサンプルアプリケーションを紹介します。まずはDisplayTagExのライブデモを見てください(画面例は図1を参照)。DisplayTagExアプリケーションでは、1つ1つの注文ごとに明細行をグループ化し、各グループの小計を示します。ユーザーが行のどこかをクリックすると、選択した項目に関する詳細情報のページにジャンプします。リスト1とリスト2に、それぞれ「OrderDetails.jsp」と「displaytagex.css」のソースを示します。
図1 DisplayTagライブラリと簡単なJavaScriptで高機能のテーブルを実現したDisplayTagExサンプルアプリケーション
リスト1 図1のテーブルを生成するための<display>コード(「OrderDetails.jsp」より)
<display:table name="${orderDetails}" export="true" id="row"
class="dataTable" defaultsort="1" defaultorder="ascending"
pagesize="16" cellspacing="0"
decorator="org.displaytag.decorator.TotalTableDecorator">
<display:column property="id" title="ID" class="hidden"
headerClass="hidden" media="html" />
<display:column property="customerName" title="Customer"
sortable="true" group="1" class="customer"
headerClass="customer" />
<display:column property="orderNumber" title="Order Number"
sortable="true" group="2" class="orderNumber"
headerClass="orderNumber" />
<display:column property="orderDate" title="Order Date"
sortable="true" format="{0,date,short}" group="3"
class="orderDate" headerClass="orderDate" />
<display:column property="productName" title="Product"
sortable="true" class="productName" />
<display:column property="quantity" title="Quantity"
sortable="true" class="quantity" headerClass="quantity" />
<display:column property="total" title="Line Item Total"
sortable="true" format="{0,number, currency}" total="true"
class="lineItemTotal" headerClass="lineItemTotal" />
</display:table>
リスト2 Displaytagex.css
.dataTable {
background-color: white;
border: 1px solid #000066;
font-size : 9pt;
margin: 5px;
width:100%;
}
.dataTable th {
border-right: 1px solid #c8c8ff;
padding-left: 2px;
padding-right:12px;
font-family: arial, helvetica, sans-serif;
font-weight: bold;
color: white;
background-color: #000066;
margin-right: 10px;
white-space: nowrap;
}
.dataTable td {
font-family: verdana, arial, helvetica, sans-serif;
padding-left: 2px;
}
.dataTable tr.total td {
white-space: nowrap;
vertical-align: top;
font-weight: bold;
border-top: 1px solid black;
padding-bottom: 10px;
}
.dataTable tr.total td.customer {
visibility: hidden;
}
.dataTable td.hidden {
display: none;
}
.dataTable th.hidden {
display: none;
}
.dataTable th.r {
text-align: right;
padding-right: 10px;
}
.dataTable th.c {
text-align: center;
}
.dataTable td.r {
text-align: right;
padding-right: 10px;
}
.dataTable td.c {
text-align: center;
}
.dataTable tr.odd {
background-color: whitesmoke;
}
.dataTable tr.even {
background-color: #d6cfe6;
}
.dataTable th a ,.dataTable th a:visited {
text-align: left;
color: white;
}
.dataTable th a:hover {
color: #ffcc00;
background-color: transparent;
}
.dataTable .order1 {
background-position: right;
background-image: url(../images/arrow_up.gif);
background-repeat: no-repeat;
}
.dataTable .order2 {
background-position: right;
background-image: url(../images/arrow_down.gif);
background-repeat: no-repeat;
}
.pagelinks {
color: #999999;
margin: 5px;
}
.pagelinks img {
vertical-align: middle;
}
span.export {
padding: 0 4px 1px 20px;
font-size: x-small;
text-align: center;
}
span.excel {
background-image: url(../images/ico_file_excel.png);
background-repeat: no-repeat;
width: 16px;
}
span.csv {
background-image: url(../images/ico_file_csv.png);
background-repeat: no-repeat;
width: 16px;
}
span.xml {
background-image: url(../images/ico_file_xml.png);
background-repeat: no-repeat;
width: 16px;
}
span.pdf {
background-image: url(../images/ico_file_pdf.png);
background-repeat: no-repeat;
width: 16px;
}
span.rtf {
background-image: url(../images/ico_file_rtf.png);
background-repeat: no-repeat;
width: 16px;
}
.dataTable tr.rowMouseOver {
background-image: url(../images/selected.gif);
background-repeat: repeat-x;
background-color: #ffff99;
}
html,body {
width: 100%;
height: 100%;
margin: 0px;
font-family: arial, helvetica, sans-serif;
font-size: 9pt;
}
.pageHeader {
height: 60px;
background-image: url(../images/header.gif);
background-repeat: repeat-x;
}
.pageHeaderText {
font-size: 30px;
margin-left: 5px;
color: whitesmoke;
font-family: "century gothic", verdana, arial, helvetica,
sans-serif;
font-weight: bold;
text-align: left;
display:inline;
white-space: nowrap;
}
.leftMenu {
white-space: nowrap;
height: 100%;
background-color: #d6cfe6;
font-family: verdana, arial, helvetica, sans-serif;
}
.content {
width: 100%;
padding: 10px;
}
.rightColumn {
width: 3px;
}
.customer {
width: 20%;
text-align: left;
}
.orderNumber {
width: 10%;
text-align: left;
}
.orderDate {
text-align: center;
width: 10%;
}
.productName {
width: 25%;
}
.quantity {
text-align: center;
width: 10%;
}
.lineItemTotal {
text-align: right;
width: 15%;
padding-right:10px;
}
.logo {
border-top: 1px solid #000066;
border-left: 1px solid #000066;
border-bottom: 1px solid #000066;
display:inline;
text-align: right;
}
.code {
font-family: verdana, arial, helvetica, "courier new", monospace;
font-size: 9pt;
}
.borderedInline {
display: inline;
border: 1px solid black;
}
a:link {
color: #000099;
text-decoration: none;
background-color: transparent;
font-weight: bold;
}
a:visited {
color: #000099;
text-decoration: none;
background-color: transparent;
font-weight: bold;
}
a:hover {
color: #d68000;
background-color: transparent;
font-weight: bold;
}
img {
border: 0px;
}
本稿のサンプルアプリケーションの開発および配置に使用した環境は、DisplayTagライブラリv1.1、JDK 5.0、MyEclipse 4.1、Tomcat 5.5です。ただし、ここで紹介した手順は、JSPを使ったWebアプリケーション開発の現場ならば、どんな環境でも応用できます。
DisplayTagライブラリを使用する理由
まず、なぜDisplayTagライブラリを使用するのかを考えてみましょう。JSTLやHTMLでデータテーブルを作成してはいけないのでしょうか? そのような方法でテーブルを作成することも可能ですが、問題は、膨大な手間がかかることです。実際、動的データを表示するごく単純なテーブルを作成するにも、<table>、<tr>、<td>、<c:forEach>、<c:choose>、<c:when>、<c:out>というタグを使用しなければなりません。
1つの方法は、JSFベースのアプリケーションに切り替えることです。例えば、ApacheのTomahawkライブラリの<t:dataTable>タグを使用すれば、JSTLやHTMLのタグを使わずに済み、なおかつ列の並べ替え機能を実現できます。また、最近オープンソース化されたOracleのADFコンポーネントライブラリを使うという方法もあります。このライブラリの<af:table>タグは、列の並べ替え、ページナビゲーション、行の選択をサポートしています(しかし、テーブルデータのエクスポートには対応していません)。ただし、これらの方法には、JSFベースのアプリケーションでしか機能しないという大きな欠点があります。
ここで紹介しておきたいのが、eXtremecomponentsの<ec:table>タグです。これは我々が求める要件をほぼ満たしており、グループ化と小計の計算ができないだけです。
DisplayTagライブラリのインストール
自分で開発しているWebアプリケーションにDisplayTagの機能を追加するには、次のようにします。
- サンプルアプリケーションの「DisplayTagEx/WebContent/WEB-INF/lib」ディレクトリに含まれている一連のJARを、Webアプリケーションの「WEB-INF/lib」ディレクトリにコピーします。最新のDisplayTagライブラリと従属ファイルをdisplaytag.sourceforge.netからダウンロードしてもかまいません。
- サンプルアプリケーションの「DisplayTagEx/WebContent/css」ディレクトリを、Webアプリケーションのルートにコピーします。このディレクトリ内の「displaytagex.css」には、本稿で説明するCSSスタイルがすべて含まれています。
- サンプルアプリケーションの「DisplayTagEx/WebContent/images」ディレクトリを、Webアプリケーションのルートにコピーします。このディレクトリには、ページナビゲーション(先頭へ、前へ、次へ、末尾へ)と並べ替え(昇順/降順)の機能に使われる画像が入っています。
- サンプルアプリケーションの「DisplayTagEx/src/displaytag.properties」ファイルを、Webアプリケーションの最上位のソースディレクトリにコピーします。このプロパティファイルについては次の節で説明します。
本稿のサンプルアプリケーションを実際のアプリケーションサーバーにインストールして実行するには、次のようにします(ここではTomcat 5を想定)。
- Tomcatの「/webapps」ディレクトリの下に「DisplayTagEx」というディレクトリを作成します。
- サンプルアプリケーションの「DisplayTagEx/WebContent/」の内容をTomcatの「/webapps/DisplayTagEx」にコピーします。
- Tomcatを実行します。
- ブラウザから「http://localhost:8080/DisplayTagEx」を参照します。
基本的な使い方
実際にDisplayTagライブラリを使用するには、次の3段階のプロセスを踏みます。
- JSPの先頭でタグライブラリをインポートします。
<%@ taglib uri="http://displaytag.sf.net" prefix="display" %>
- CSSファイルにリンクします。
<link rel="stylesheet" type="text/css"
href="css/displaytagex.css">
<display:table>タグを追加し、このタグ内に<display:column>タグを記述します。例えば次のようにします。
<display:table name="${orderDetails}" class="dataTable">
<display:column property="customerName" />
<display:column property="productName" />
</display:table>
この例では、<display:table>タグは${orderDetails}というEL式からデータを取得しています。本稿のサンプルアプリケーションにおいて、orderDetailsは、コンテキストの初期化時に作成されるOrderDetailオブジェクトのArrayListを表します。ここではテーブルのclass属性をdataTableに設定していますが、これはCSSファイル「displaytagex.css」(リスト2参照)内の.dataTableスタイルに対応します。DisplayTagライブラリは、表の奇数行と偶数行のclass属性をそれぞれoddとevenに自動的に設定するので、それぞれに異なる背景色を簡単に割り当てることができます。なお、oddとevenはDisplayTag CSSのクラス名ですが、dataTableというクラス名は私が自由に付けたものです。
DisplayTagライブラリには何十という機能があり、表示しようとする個々のテーブルについて、それぞれの機能を細かく設定することができます。この設定を行う最も効率的な方法は、「displaytag.properties」ファイル(リスト3参照)に記述することです。
リスト3 Displaytag.properties
sort.amount=list
paging.banner.placement=top
paging.banner.all_items_found=
paging.banner.some_items_found=
paging.banner.one_item_found=
paging.banner.onepage=
paging.banner.full=<div class="pagelinks" align="right"><a href={1}>
<img src="images/first.gif"></a><a href={2}>
<img src="images/prev.gif"></a>{0}<a href={3}>
<img src="images/next.gif"></a><a href={4}>
<img src="images/last.gif"></a></div>
paging.banner.first=<div class="pagelinks" align="right"><a href={1}>
<img src="images/first.gif"></a><a href={2}>
<img src="images/prev.gif"></a>{0}<a href={3}>
<img src="images/next.gif"></a><a href={4}>
<img src="images/last.gif"></a></div>
paging.banner.last=<div class="pagelinks" align="right"><a href={1}>
<img src="images/first.gif"></a><a href={2}>
<img src="images/prev.gif"></a>{0}<a href={3}>
<img src="images/next.gif"></a><a href={4}>
<img src="images/last.gif"></a></div>
export.banner=<div class="pagelinks" align="right">{0}</div>
export.types=csv excel xml pdf rtf
export.excel=true
export.csv=true
export.xml=true
export.pdf=true
export.rtf=true
export.excel.class=org.displaytag.export.excel.DefaultHssfExportView
export.pdf.class=org.displaytag.export.DefaultPdfExportView
export.rtf.class=org.displaytag.export.DefaultRtfExportView
export.excel.filename=data.xls
export.pdf.filename=data.pdf
export.xml.filename=data.xml
export.csv.filename=data.csv
export.rtf.filename=data.rtf
このファイルでは、Webサイト全体で共通して適用するテーブルプロパティのデフォルト値を設定します。例えば、サンプルアプリケーションの「displaytag.properties」には、paging.banner.placement=topという行が含まれています。これは、ページナビゲーションバナーを既定でテーブルの上に配置することを意味します。特定のテーブルでこのプロパティ(またはその他のプロパティ)の既定値とは異なる設定を使用したい場合は、次のようにテーブル内に<display:setProperty>タグを入れ子で記述します。
<display:table ... >
<display:setProperty name="paging.banner.placement"
value="bottom" />
</display:table>
列の並べ替えと書式設定
テーブル内の列を並べ替え可能にするには、その列にsortable属性を指定し、<display:column sortable="true">のようにします。これにより、列の見出しが、テーブルデータを昇順または降順に並べ替えるためのハイパーリンクになります。テーブルのHTMLが生成されるときには、列見出しのセルが、<th class="order1">(昇順の場合)または<th class="order2">(降順の場合)として出力されます。このクラス名に基づき、列見出しのCSS背景イメージとして上矢印または下矢印を追加することができます。
既定では、現在表示されているページ上のデータだけが並べ替えられます(後述の「ページナビゲーション」を参照)。この動作を変更するには、「displaytag.properties」ファイルにsort.amount=listというプロパティを追加します。サンプルアプリケーションでは、実際にこの設定を採用しています。
テーブルデータを書式設定するには、書式を適用したい列にformat="Pattern"という形式で属性を追加します。Patternの部分には、有効なjava.text.MessageFormatパターンを指定できます。例えば<display:column format="{0,date,short}">とすると、2003年11月1日という日付が「11/1/03」という書式で出力されます(実際の書式はロケール設定によって異なります)。
行のグループ化と小計の計算
DisplayTagの大きな特徴の1つは、行のグループ化と小計の計算の機能を組み込みで備えていることです。例えば、数多くの注文の明細行をデータソースから取得し、その結果を表示したいとします。このような場合によく問題になるのが、同じ情報が繰り返し表示されることです。同じ注文を構成する連続した明細行では、同じ顧客名、注文番号、注文日などが繰り返されることになります。このような状態だと、どの明細行がどの注文に関連付けられているのかが判別しにくくなります。
DisplayTagは、グループ属性を持つ列(例えば<display:column group="1">の列など)を調べ、そのいずれかの列に繰り返しデータが含まれている行をすべてグループ化します。今回のサンプルアプリケーションでは、Customer列をgroup="1"、Order Number列をgroup="2"、Order Date列をgroup="3"としているため、この3つの列の組み合わせによって行がグループ化されます(リスト1参照)。
また、テーブルにTotalTableDecoratorというテーブル装飾を指定していることに注目してください。これにより、total属性を持つ列の値がグループごとに合計され、その合計値がグループの下に独立した行として表示されます。この小計行には、DisplayTagによってtotalというCSSクラスが与えられるため、このクラスを通じて特殊なスタイルを簡単に適用することができます。こうしたテーブル装飾を指定するには、<display:table decorator="org.displaytag.decorator.TotalTableDecorator">とします。また、列の小計を有効にするには<display:column total="true">とします。
ページナビゲーション
DisplayTagでは、<display:table pagesize="16">のようにpagesize属性を指定することで、テーブルにページナビゲーションを追加することができます。ページナビゲーションの際に遭遇し得るさまざまな状況(例えばデータが1ページ分しかない場合や、複数ページあるうちの先頭、中間、最終ページを表示する場合など)に対処できるようにするために、DisplayTagでは、paging.banner.onepage、paging.banner.first、paging.banner.fullのようなプロパティが用意されています(リスト3参照)。
これらのプロパティの使い方を示すために、具体的な例を1つ紹介しておきます。
paging.banner.full=<div class="pagelinks" align="right"><a href={1}>
<img src="images/first.gif"></a><a href={2}>
<img src="images/prev.gif"></a>{0}<a href={3}>
<img src="images/next.gif"></a><a href={4}>
<img src="images/last.gif"></a></div>
このやや複雑なプロパティは、すべてのページングリンクを表示するときに、図2のようなバナーを出力することをDisplayTagに指示しています。
図2 DisplayTagExのすべてのページングリンクを表示するページングバナー
ここでは、<div>を使用することで、ページングバナーにpagelinksというCSSスタイルを割り当てています。{1}は、データの先頭ページへのリンクを表すプレースホルダです。ここでは、クリッカブルイメージのターゲットURLとして使用されています。{2}、{3}、{4}は、それぞれ前ページ、次ページ、最終ページについてのプレースホルダです。{0}は、一連の番号付きページへのリンクを出力する特殊なプレースホルダです。
Excel、PDFなどへのエクスポート
DisplayTagで表現したテーブルのデータをCSV(Comma Separated Value)、XML、Excel、PDF、RTFにエクスポートするには、テーブル属性を1つ追加し、プロパティをいくつか設定し、それぞれのエクスポートタイプについて<span>スタイルを作成する必要があります。
まず、テーブルにexport属性を追加すると、DisplayTagによってエクスポート用のバナーが表示されます。例えば、<display:table export="true">のようにします。
ページナビゲーションのときと同様に、DisplayTagにはエクスポート関連のプロパティが数多く用意されています。既定のプロパティは「displaytag.properties」ファイルで設定します。サポートされているすべての形式をユーザーが使用できるようにする場合は、通常はexport.bannerプロパティとexport.format.filenameプロパティを設定します。
例えば、今回のサンプルアプリケーションでは、エクスポート形式のリスト(エクスポートバナー)を右端に配置し、このリストにpagelinksというCSSクラスを適用したいので、「displaytag.properties」ファイルに次のように記述しています(リスト3参照)。
export.banner=<div class="pagelinks" align="right">{0}</div>
この設定だけを見ると少々分かりにくいのですが、エクスポートバナーが描画されるときには、個々のエクスポート形式のハイパーリンクが「export format」クラスの<span>スタイル(例えば<span class="export excel">など)の中に挿入されます。そのため、サンプルアプリケーションでは、それぞれの形式に対応するCSSクラスを次のような形で用意しています。
span.excel {
background-image: url(../images/ico_file_excel.png);
background-repeat: no-repeat;
width: 16px;
}
エクスポートしたデータのファイル名を設定するには、export.format.filenameプロパティをexport.pdf.filename=data.pdfのように指定します。
Javascriptによる行操作
今回のサンプルアプリケーションのもう1つの要件は、ユーザーが行をマウスポインタで指すとその行が強調表示され、行のどこかをクリックすると、その行についての識別情報を含んだ新しい要求が生成されるようにすることです。この要件を満たすには、何らかのJavaScriptを取り入れる必要があります。
サンプルアプリケーションでは、「RowHandlers.js」というJavaScriptファイルを用意し(リスト4参照)、その中にaddRowHandlers()という関数を作成しました。この関数は、HTMLテーブル内の各行に次の3つのイベントハンドラを追加します。
- onmouseover ―― 行のクラス属性を保存し、別の背景色または画像を持つ新しいスタイルへと変更します。
- onmouseout ―― 以前のクラス属性を復元します。
- onclick ―― 指定のURLにジャンプします。このとき、選択されたテーブル行に関する要求パラメータをURLに付加します。
リスト4 Rowhandlers.js
// Adds onmouseover, onmouseout, and onclick handlers
// to each table row.
// The onmouseover handler changes the row’s class attribute to
// rowMouseOver. The onmouseout handler changes it back.
// The onclick function makes a request for the specified url,
// including the innerHTML of the specified column
// as a request parameter.
function addRowHandlers(tableId, rowClassName, url,
paramName, columnIndex) {
var previousClass = null;
var table = document.getElementById(tableId);
var rows = table.getElementsByTagName("tr");
for (i = 1; i < rows.length; i++) {
rows[i].onmouseover = function () {
previousClass = this.className;
this.className = this.className + " " + rowClassName ;
};
rows[i].onmouseout = function () {
this.className = previousClass;
};
rows[i].onclick = function () {
var cell = this.getElementsByTagName("td")[columnIndex];
var paramValue = cell.innerHTML;
location.href = url + "?" + paramName + "=" + paramValue;
};
}
}
この「RowHandlers.js」を自作のJSPで利用するには、次のようにします。
<head>セクション内でこのスクリプトにリンクします。
<script src="js/RowHandlers.js" language="javascript"
type="text/javascript" /></script>
<body>タグのonload属性でaddRowHandlers()を呼び出します。
<body onload= "addRowHandlers(’row’, ’rowMouseOver’,
’OrderDetail.jsp’, ’id’, 0)">
手順2のタグの意味を解釈してみましょう。まず、ページの<body>セクションがロードされるときに、rowというidを持つテーブルに対してaddRowHandlers()関数が呼び出され、この関数によってテーブルの各行に3つのハンドラが追加されます。これらのハンドラが適用された場合、ユーザーがいずれかの行をマウスポインタで指すと、その行のCSSクラス属性がrowMouseOverに変更されます。マウスポインタが行から外れると、CSSクラス属性は元の値に戻ります。ユーザーが行をクリックすると「OrderDetail.jsp」が呼び出されますが、この呼び出し要求にはパラメータidの値が付加されます(この値は、クリックされた行の列0から取得されます)。
サンプルアプリケーションでは、「OrderDetails.jsp」(リスト1参照)が前述の「RowHandlers.js」を使用しています。また、「OrderDetails.jsp」では、行IDの値を非表示の列に配置するという手法を使っています。この列を非表示にするために、<display:column>のclassおよびheaderClass属性をhiddenに設定しています(hiddenは、displayプロパティがnoneに設定されているCSSスタイルです)。これは、要求スコープで使用するデータをユーザーの目から隠したいときに役立つ、単純ながら効果的な方法です。
DisplayTagは、JSP内にテーブルデータを簡単に表示したいときに役立つオープンソースのタグライブラリです。このライブラリは幅広い用途に利用でき、例えば検索アプリケーションでは検索結果を特定の列で並べ替え、そこから製品リストにジャンプさせるページナビゲーションを実現できますし、財務レポートアプリケーションでは数値の書式設定、グループ集計、PDFへのエクスポートの機能を利用できます。可能性は無限にあり、おそらく読者の皆さんが開発しているWebアプリケーションでも、DisplayTagがお役に立つでしょう。
まずサンプルアプリケーションをダウンロードし、実際に試してみてください。その後、「OrderDetails.jsp」をテンプレートとして使い、目的に合ったダイナミックテーブルを作成してみましょう。ダウンロードサンプルに収録されているCSSファイル、JavaScriptファイル、プロパティファイル、画像ファイルは、そのままの形で利用しても、個々のアプリケーションに合わせて必要に応じて改変してもかまいません。