JSTL(JSP Standard Tag Library)とは、よく需要があるJSPのカスタムタグをSunとJavaコミュニティが汎用化し、
標準と定めたライブラリ(の仕様の集合体)です。
JSTLはCore、XML、SQL、Formatting、Functionsなどに分けられ、
これらの中でも最も基本的なタグライブラリがCoreです。
そして、JSTL仕様のオープンソースの実装として有名なのが、
Jakarta Taglibsです。
Taglibsには、JSTLの各ライブラリの実装のほか、
独自に拡張した20種類以上のタグライブラリが揃っており、便利に利用できます。
ここでは、Taglibsに含まれているCoreタグライブラリを使った例をご紹介します。
●Coreタグライブラリのセットアップ手順
以下の説明では、タグライブラリを使いたいWebアプリケーションの、
コンテキストルートのディレクトリを$APP_HOME と表記します。
-
ホームページ
から「Download」ページを辿り、nightly build
jakarta-taglibs-YYYYMMDD.zip をダウンロードして、展開します。
-
jakarta-taglibs/standard/lib ディレクトリのjstl.jar と standard.jar を
$APP_HOME/WEB-INF/lib にそれぞれ taglibs-jstl.jar、taglibs-standard.jar という名前でコピーします。
(※このJARファイルの名前は自由です。)
-
最近のtaglibsのCoreタグライブラリでは、
$APP_HOME/WEB-INF/web.xmlにtaglibを記述する必要がなくなりました。
また、同梱の*.tldファイルを、$APP_HOME/WEB-INFにコピーする必要もありません。
JARファイルのMETA-INFフォルダの中にTLDファイルを置いてあるライブラリの場合、
web.xmlへのtaglib記述も、TLDファイルのWEB-INFへの配置も、両方不要になったためです。
ただし、各JSP文書でのURIは「/WEB-INF/c.tld」みたいなURIでは駄目で、
必ず標準のものを使う必要があります。
JSTL 1.0と1.1では標準URIは異なるので注意して下さい。
1.0用
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
1.1用
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
●サンプル1(Hello, World)
<%@ page language="java" contentType="text/html;charset=Shift_JIS" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html><body>
<font color="#700040">●Jakarta Taglibs JSTL Core(c)のテスト</font><br>
<c:set var="hello1" value="Hello, World" />
<c:set var="hello2">
<font color="red">Hello, World(red)</font>
</c:set>
<c:out value="${hello1}"/><br>
<c:out value="${hello2}" escapeXml="false"/><br>
</body></html>
|
最も基本的なc:setタグとc:outタグの使用例です。
c:setタグはvar=で指定した変数に値をセットします。
ここでは書いていませんが、Beanオブジェクトにプロパティ値をセットすることもできます。
c:outタグはvalue=で指定した値をJSPストリームに出力します。
${変数名}とすると変数の参照ができます。
この、中括弧{}は省略できないので注意してください。
省略して「$hello1」と書くと、そのまま「$hello1」が表示されることになります。
また、escapeXml = "false"
にすると、値の中にHTMLタグが含まれていると、それを展開せずにそのまま出力します
(つまり、HTMLタグが効果を表します)。
escapeXml = "true" とすると、HTMLタグは自動的にエスケープされます
(こちらがデフォルト)。
●サンプル2(暗黙オブジェクトの使用)
<%@ page language="java" contentType="text/html;charset=Shift_JIS" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html><body>
<font color="#700040">●Jakarta Taglibs JSTL Core(c)のテスト</font><br>
<c:choose>
<c:when test="${param.username == null}">
<form action="<c:out value="${pageContext.request.requestURI}" />" method="post">
名前を入力して下さい。
<input type="text" name="username"></input><br>
<input type="submit" value="OK"></input>
</form>
</c:when>
<c:when test="${param.username == ''}">
おおっと。名前を入力して下さい。
<a href="<c:out value="${pageContext.request.requestURI}" />">
戻る
</a>
</c:when>
<c:otherwise>
こんにちは、<font color="red"><c:out value="${param.username}"/></font>さん。
</c:otherwise>
</c:choose>
</body></html>
|
JSPにもout、request、responseなど、
最初から使える暗黙オブジェクトが幾つかありますが、
JSTLでも同様に、フォーム変数を表すparam、
サーブレットではgetPageContext()で取得できるpageContext、
HTTPヘッダを表すheaderなど幾つかの暗黙オブジェクトがサポートされています。
これらを使うと、記述を大幅に簡略化できます。
例えば、フォーム変数usernameの値は${param.username}で参照できます。
このJSPページのURIは${pageContext.request.requestURI}で参照できます(※1)。
(※1) 後述のように、JSPの暗黙オブジェクトの一覧と、JSTLの暗黙オブジェクトのそれは同じものではありません。
JSTLには、request、sessionなどの暗黙オブジェクトはありません。
代わりに、「pageContext.request」のような式で設定する必要があります。
CoreタグライブラリにはifやforEachのような制御構造を実装したタグも幾つかあります。
このサンプルではc:choose、
c:when、c:otherwise
のタグを使って、CやJavaでいうswitch文のような複数条件分岐を行っています。
この他に、c:ifというタグもありますが、
XSLTのxsl:ifと同じくelseがないので、
一方向の条件分岐の場合にのみ使うことになるでしょう。
なお、これらのタグの条件式を指定する属性は、
XSLTと同じくtestです。
一部selectと書いているドキュメントもありますが、typoのようです。
●サンプル3(リダイレクトと画面渡りの例)
<%@ page language="java" contentType="text/html;charset=Shift_JIS" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html><body>
<font color="#700040">●Jakarta Taglibs JSTL Core(c)のテスト</font><br>
<h2>暗黙オブジェクトparamとheaderのテスト</h2>
<c:choose>
<c:when test="${param.username == null}">
<form action="<c:out value="${pageContext.request.requestURI}" />" method="post">
名前を入力して下さい。
<input type="text" name="username"></input><br>
<input type="submit" value="OK"></input>
</form>
</c:when>
<c:when test="${param.username == ''}">
おおっと。名前を入力して下さい。
<a href="<c:out value="${pageContext.request.requestURI}" />">
戻る
</a>
</c:when>
<c:otherwise>
<!--
c:redirectタグはパラメータをGETメソッドで渡すのに対し、
jsp:forwardはPOSTで渡します。
-->
<jsp:forward page="redirect2.jsp">
<jsp:param name="username" value="<%= request.getParameter("username") %>" />
</jsp:forward>
</c:otherwise>
</c:choose>
</body></html>
|
<%@ page language="java" contentType="text/html;charset=Shift_JIS" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html><body>
アプリケーションメインページ
<%
String referer = request.getHeader("Referer");
String username = request.getParameter("username");
%>
チェック情報:
[<%= referer %>] [<%= username %>] <br>
<%
if(referer == null || username == null ||
! referer.equals("http://localhost:8080/app/redirect1.jsp")){
%>
<font color="red">ログインしていません。</font>
<%
}
%>
</body></html>
|
上のサンプル2を改造して、最初のページ(redirect1.jsp)でユーザ名が入力されると、
2つめのページ(redirect2.jsp)からメインのアプリケーションが始まる、
という感じにしています。
redirect1.jspからredirect2.jspにジャンプさせるのに、
ここではjsp:forwardタグを使っています。
Coreタグライブラリの中には
c:redirectタグという、同様の機能をもつタグもあるのですが、
c:redirectタグを使うと、パラメータをGET方式で、
つまりURLの後ろに?パラメータ名=値、の形でつけてしまうので、
パラメータの内容が予測されてしまうという欠点があります。
jsp:forwardタグでパラメータを(jsp:paramで)指定した場合には、
パラメータがURLに含まれることはありませんので、こちらを使っています。
Coreタグライブラリのc:setタグで使用する変数の正体は、
JSPのページコンテキストに記憶される属性変数です。そのため、JSPのコード・ブロックから、
pageContext.getAttributeメソッドと
pageContext.setAttributeメソッドを使ってアクセスできます。
次の例はこの典型的な使い方のひとつです。
通常Webフォームなどでセットし渡されるリクエスト・パラメータはCoreタグライブラリの式(EL)の中で
${param.パラメータ名} で参照できますが、
パラメータのエンコーディングを変換する必要がある場合に、
その変換をJSPコード・ブロックで行い、ページコンテキストの属性にセットしています。
<%
String param = request.getParameter("word");
param = new String(param.getBytes("8859_1"), "UTF-8");
pageContext.setAttribute("word", param);
%>
パラメータ:<c:out value="${word}"/><br/>
|
●c:forEachタグのテンプレート
<c:forEach var="e" items="${mapList}" varStatus="loop">
<tr>
<td>${loop.count}</td>
<td><html:checkbox property="selectedMap(${e['shitenCode']})" value="${e['shitenCode']}" /></td>
<td>${e["shitenCode"]}</td><td>${e.shitenName}</td><td>${e['address']}</td>
</tr>
</c:forEach>
|
c:forEachタグには上記のほかに、begin、end、stepの属性もあり、
それぞれループ開始要素のインデックス、ループ終了要素のインデックス、増分です。
またvarStatusで指定した変数には、ループに関する情報が自動的に設定され、
上の例のようにEL式で参照できます。
current | 現在処理対象の要素 |
index | 0から始まる連番 |
count | 1から始まる連番 |
first | 最初の要素なら真(boolean) |
last | 最後の要素なら真(boolean) |
begin | begin属性の値=ループカウンタの初期値 |
end | end属性の値=ループカウンタの最後の値 |
step | step属性の値=ループカウンタの増分 |
値0から9までループする例は以下のようになります。
変数loop.indexには0から9の値が毎回設定されるので、
+1すると1から10の値が表示されることになります。
<c:forEach begin="0" end="9" step="1" varStatus="loop">
<tr>
<td align="right">${loop.index + 1}</td>
<td>******</td>
</tr>
</c:forEach>
|
●c:forEachタグのリストを文字列で書く場合の注意
c:forEachタグのリスト(コレクション)を書くとき、
ListやIteratorオブジェクトを指定するか、またはカンマで区切った文字列を指定できますが、
この文字列を指定するとき、
カンマの前後に空白文字を入れるとその空白文字もループ変数の値に含まれます。
例えば、
<c:forEach var="index" items="01, 02, 03, 04, 05">
|
のように書くと、「01」だけは前後に空白が入りませんが、02から05までは「0」
の前に空白文字が1個入ってしまい、おそらく期待した処理が出来ないでしょう。
●request属性、session属性、application属性の参照
HttpServletRequest.setAttribute()、
HttpSession.setAttribute()、ServletContext.setAttribute()
でそれぞれセットできる各スコープの属性は、Coreタグの式内で次のように簡単に参照できます。
<c:out value="${requestScope.shiten}" />
<c:out value="${sessionScope.shiten}" />
<c:out value="${applicationScope.shiten}" />
|
「request.」や「session.」ではなくて「requestScope.」なので注意してください。
うっかりしていると、忘れてしまうのですよ。
なお、JSP 2.0の今では、それらはそれぞれただ「${shiten}」とだけ書けばOKです。
●c:forEachタグでリクエスト・パラメータ(param変数)を参照する
リクエスト・パラメータ(param変数)
の一覧を取得するという処理は汎用的なツールを使うときにしか必要にならないためか、
あまりワールド・ワイド・ウェブ上には例が載っていないですが、
一応次のようにすると可能でした。
<c:forEach var="e" items="${param}">
${e.key} | ${e.value}<br/>
</c:forEach>
|
●リストの要素数(size)を参照するには?
JSTLになくて困る機能の筆頭が、オブジェクトがListの場合に、
その要素の数を参照する演算子がないことです。
Listのsize()メソッドがgetなにがしというJavaBeansの命名通りでないためですが、
これはJSTL 1.1で設けられたFunctionsライブラリ(EL関数)の機能を使って、次のようにすれば解決できます。
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
...(中略)
${fn:length(TableForm.recordList)}行のデータが見つかりました。<br/>
|
ここでは、session.getAttribute("TableForm").getRecordList()が返す値がListのサブクラスです。
●EL関数の一覧
JSTL標準FunctionsでサポートされているEL関数のリストは、
こちらです。ホントに基本的な文字列処理ばかりですね。
少し凝った処理をしたくなったらどうしたらよいんでしょう。まったく、もー
●EL関数の自作
と、というわけで…
上記のfn:lengthのように、式言語(EL)の中で使える関数を自作することも可能です。
それは、Javaのstaticメソッドとして実装します。
AtmarkITのこのページ
に実装例が載っています。なお、TLDに書くメソッドのシグネチャは、
間違えないように書かないといけません。
一致しないと呼び出してくれず、(普通の設定だと多分)
サーブレットコンテナのログにも出力されないので注意して下さい。
●EL式でrequestオブジェクトは参照できる?
参照できますが、JSPとは異なり、
EL式にはrequestやsession等の暗黙変数はありません。
(requestScopeやsessionScope、pageContext等の変数はある)
従って、EL式の中で「request」と書いても効きません
(エラーにはなりませんが、nullで初期化された新しい変数とみなされ、
期待通りの動きはしない)。正解は、「pageContext.request」です。
PageContextクラスにはgetRequest()メソッドがあるので、
上記の記法で取り出せるのです。
●EL式で変数同士の数値比較をするには?
<c:if test="${value > 10}">
|
とすると、右辺が数値のためvalueの値が数値に変換され、
数値同士の比較が行われます。では変数同士の場合は?
value1が"6"、value2が"10"としますと
<c:if test="${value1 gt value2}">
|
この式の値は真("6"の方が大きい)という結果になってしまいます。
文字列として比べるとASCIIコード順なので6が1より大きいためです。
では数値として変数同士を比較するには?答えは
<c:if test="${value1 gt 0+value2}">
|
片方が数値だと数値に寄せて比較されます。
ELでは、オペランドの片方が数値だと、文字列が数値に変換されるという特性があります。
ELの演算子の性質については、
(株)ヴィザーズのJava Sticky Noteのこのページ
に詳しい説明があります。
なお、これは全てのプログラミング言語にはあてはまりません。
別のページに書いていますが、JavaScriptは正反対で、
数値が文字列に合わされるという性質があります。
●このエラーはなんぞ?
javax.servlet.jsp.JspTagException: <forEach> 内で供給された "items" を反復処理する方法が不明です
これは、c:forEachタグのitems属性で指定した値(requestスコープなどの属性名)が参照するオブジェクトが、
コレクション型(Listや配列)ではないクラスのインスタンスのため、
c:forEachタグで処理できない、という意味です。
(first uploaded 2003/02/23 last updated 2009/10/26, URANO398)
|