0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Java研修】Servlet/JSP入門⑧ EL式とJSTL

0
Posted at

はじめに

Servlet/JSP入門の第8回は EL式(Expression Language)とJSTL(JSP Standard Tag Library) です。

これまでのJSPでは <% ... %> のスクリプトレットを多用してきましたが、実はスクリプトレットは 現在では非推奨 です。代わりに、EL式とJSTLを使うことで、より読みやすくメンテナンスしやすいJSPを書けます。

第8回で学ぶこと

  • スクリプトレットの問題点
  • EL式(Expression Language)の基本構文
  • リクエスト属性・セッション属性へのアクセス
  • JavaBeanプロパティ、Map、Listへのアクセス
  • JSTLコアタグ(c:if, c:choose, c:forEach, c:set, c:out)
  • JSTLフォーマットタグ(fmt:formatDate, fmt:formatNumber)
  • スクリプトレットからEL+JSTLへのリファクタリング

1. スクリプトレットの問題点

Before:スクリプトレットだらけのJSP

<%@ page import="java.util.List" %>
<%@ page import="model.User" %>
<%
    List<User> users = (List<User>) request.getAttribute("users");
    String message = (String) request.getAttribute("message");
%>
<html>
<body>
    <% if (message != null) { %>
        <p><%= message %></p>
    <% } %>

    <table>
        <% for (User user : users) { %>
        <tr>
            <td><%= user.getName() %></td>
            <td><%= user.getAge() %></td>
        </tr>
        <% } %>
    </table>
</body>
</html>

問題点

問題 説明
可読性が低い <% %> でJavaとHTMLが入り交じる
デザイナーと分業できない HTMLを編集するデザイナーがJavaコードを壊す危険
テストが困難 JSP内のロジックを単体テストできない
セキュリティリスク <%= %> はXSS対策がない(HTMLエスケープされない)

After:EL式+JSTLを使ったJSP

<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<html>
<body>
    <c:if test="${not empty message}">
        <p><c:out value="${message}" /></p>
    </c:if>

    <table>
        <c:forEach var="user" items="${users}">
        <tr>
            <td><c:out value="${user.name}" /></td>
            <td><c:out value="${user.age}" /></td>
        </tr>
        </c:forEach>
    </table>
</body>
</html>

Javaコードが一切なくなり、HTMLタグと同じ記法で読めるようになりました。


2. EL式(Expression Language)の基本

EL式とは?

EL式${式} の形式で、JSPページ内でデータを参照するための式言語です。

<!-- EL式の基本形 -->
${expression}

Servletで属性をセット → JSPでEL式で参照

Servlet側:

@WebServlet("/el-demo")
public class ELDemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // リクエスト属性にデータをセット
        request.setAttribute("userName", "田中太郎");
        request.setAttribute("age", 25);
        request.setAttribute("isAdmin", true);

        request.getRequestDispatcher("/WEB-INF/jsp/elDemo.jsp").forward(request, response);
    }
}

JSP側(elDemo.jsp):

<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<body>
    <h1>EL式のデモ</h1>
    <p>名前: ${userName}</p>
    <p>年齢: ${age}</p>
    <p>管理者: ${isAdmin}</p>
    <p>年齢 + 5 = ${age + 5}</p>
</body>
</html>

出力:

名前: 田中太郎
年齢: 25
管理者: true
年齢 + 5 = 30

EL式の演算子

演算子 意味
+, -, *, / 算術演算 ${a + b}
div, mod 除算、剰余 ${10 div 3}3.3333...(浮動小数点除算)
==, eq 等値比較 ${name == '田中'}
!=, ne 不等比較 ${name != '田中'}
<, lt より小さい ${age lt 30}
>, gt より大きい ${age gt 20}
<=, le 以下 ${age le 25}
>=, ge 以上 ${age ge 18}
&&, and 論理AND ${a && b}
` , or`
!, not 論理NOT ${not isAdmin}
empty null/空チェック ${empty name}
? : 三項演算子 ${age >= 20 ? '成人' : '未成年'}

3. EL式でのデータアクセス

3.1 JavaBeanプロパティへのアクセス

Servletでセットしたオブジェクトの getterメソッド に自動でアクセスできます。

User.java(JavaBean):

package model;

public class User {
    private String name;
    private int age;
    private String email;

    // getter / setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

Servlet側:

User user = new User();
user.setName("田中太郎");
user.setAge(25);
user.setEmail("tanaka@example.com");
request.setAttribute("user", user);

JSP側:

<!-- user.getName() が呼ばれる -->
<p>名前: ${user.name}</p>

<!-- user.getAge() が呼ばれる -->
<p>年齢: ${user.age}</p>

<!-- user.getEmail() が呼ばれる -->
<p>メール: ${user.email}</p>

ポイント: ${user.name} は内部的に user.getName() を呼んでいます。EL式ではgetterの get を省略した プロパティ名 で参照します。

3.2 Mapへのアクセス

// Servlet側
Map<String, String> config = new HashMap<>();
config.put("theme", "dark");
config.put("language", "ja");
request.setAttribute("config", config);
<!-- JSP側 -->
<p>テーマ: ${config.theme}</p>
<p>テーマ: ${config["theme"]}</p>  <!-- こちらの書き方もOK -->
<p>言語: ${config.language}</p>

3.3 Listへのアクセス

// Servlet側
List<String> fruits = List.of("りんご", "バナナ", "みかん");
request.setAttribute("fruits", fruits);
<!-- JSP側 -->
<p>先頭: ${fruits[0]}</p>
<p>2番目: ${fruits[1]}</p>
<p>3番目: ${fruits[2]}</p>

3.4 スコープの検索順序

EL式で ${userName} と書いた場合、以下の順序でスコープを検索します。

優先順位 スコープ EL暗黙オブジェクト 説明
1 page pageScope そのJSPページ内のみ
2 request requestScope 1リクエスト内
3 session sessionScope セッション内
4 application applicationScope アプリ全体

スコープを明示する場合:

<!-- リクエスト属性を明示的に参照 -->
${requestScope.userName}

<!-- セッション属性を明示的に参照 -->
${sessionScope.loginUser}

3.5 EL式の暗黙オブジェクト

暗黙オブジェクト 説明
param Map<String,String> リクエストパラメータ
paramValues Map<String,String[]> リクエストパラメータ(複数値)
header Map<String,String> リクエストヘッダー
cookie Map<String,Cookie> クッキー
pageContext PageContext ページコンテキスト
<!-- リクエストパラメータを直接参照 -->
<p>検索キーワード: ${param.keyword}</p>

<!-- コンテキストパスを取得 -->
<a href="${pageContext.request.contextPath}/users">ユーザー一覧</a>

4. JSTLの導入

JSTLとは?

JSTL(JSP Standard Tag Library) は、JSPでよく使う処理(条件分岐、繰り返し、フォーマットなど)を カスタムタグ として提供するライブラリです。

JSTLの導入方法

JARファイルをプロジェクトに追加する必要があります。

Maven の場合(pom.xml):

<!-- JSTL API -->
<dependency>
    <groupId>jakarta.servlet.jsp.jstl</groupId>
    <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
    <version>3.0.0</version>
</dependency>

<!-- JSTL 実装 -->
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>jakarta.servlet.jsp.jstl</artifactId>
    <version>3.0.1</version>
</dependency>

手動の場合は、上記2つのJARをダウンロードして WEB-INF/lib/ に配置します。

JSTLのタグライブラリ

プレフィックス URI(Tomcat 10+) 用途
c jakarta.tags.core コアタグ(条件分岐、繰り返し等)
fmt jakarta.tags.fmt フォーマットタグ(日付、数値等)
fn jakarta.tags.functions 関数タグ(文字列操作等)

タグライブラリの宣言

JSPファイルの先頭に taglib ディレクティブを追加します。

<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
<%@ taglib prefix="fn" uri="jakarta.tags.functions" %>

5. JSTLコアタグ

5.1 c:out - 値の出力

<!-- HTMLエスケープ付きで出力(XSS対策) -->
<c:out value="${user.name}" />

<!-- デフォルト値の指定 -->
<c:out value="${user.nickname}" default="未設定" />

なぜ ${user.name} ではなく <c:out> を使うのか?

書き方 HTMLエスケープ XSS対策
${user.name} されない なし(危険)
<c:out value="${user.name}" /> される あり(安全)

例:ユーザーが名前に <script>alert('XSS')</script> と入力した場合

${user.name}                → スクリプトが実行される(XSS攻撃成功)
<c:out value="${user.name}" /> → &lt;script&gt;... と表示される(安全)

5.2 c:set - 変数の設定

<!-- 変数の設定 -->
<c:set var="greeting" value="こんにちは" />
<p>${greeting}</p>

<!-- スコープを指定 -->
<c:set var="count" value="${count + 1}" scope="session" />

<!-- オブジェクトのプロパティを設定 -->
<c:set target="${user}" property="name" value="新しい名前" />

5.3 c:if - 条件分岐

<!-- 単純な条件分岐 -->
<c:if test="${user.age >= 20}">
    <p>${user.name}さんは成人です。</p>
</c:if>

<!-- 空チェック -->
<c:if test="${not empty users}">
    <p>${fn:length(users)}件のユーザーが見つかりました。</p>
</c:if>

<!-- 文字列比較 -->
<c:if test="${user.role == 'admin'}">
    <a href="/admin">管理画面</a>
</c:if>

注意: c:ifelse はありません。else が必要な場合は c:choose を使います。

5.4 c:choose / c:when / c:otherwise - 複数条件分岐

<c:choose>
    <c:when test="${user.age < 13}">
        <p>子供料金: 500円</p>
    </c:when>
    <c:when test="${user.age < 18}">
        <p>学生料金: 800円</p>
    </c:when>
    <c:when test="${user.age >= 65}">
        <p>シニア料金: 800円</p>
    </c:when>
    <c:otherwise>
        <p>一般料金: 1,200円</p>
    </c:otherwise>
</c:choose>

Javaの if-else if-elseswitch に相当します。

5.5 c:forEach - 繰り返し

リストの繰り返し:

<table>
    <tr><th>名前</th><th>年齢</th><th>メール</th></tr>
    <c:forEach var="user" items="${users}">
        <tr>
            <td><c:out value="${user.name}" /></td>
            <td>${user.age}</td>
            <td><c:out value="${user.email}" /></td>
        </tr>
    </c:forEach>
</table>

インデックス付き:

<c:forEach var="user" items="${users}" varStatus="status">
    <tr class="${status.index % 2 == 0 ? 'even' : 'odd'}">
        <td>${status.count}</td>  <!-- 1から始まる番号 -->
        <td><c:out value="${user.name}" /></td>
    </tr>
</c:forEach>
varStatus属性 説明
index int 0始まりのインデックス
count int 1始まりの番号
first boolean 最初の要素かどうか
last boolean 最後の要素かどうか

数値の繰り返し:

<!-- 1から10まで -->
<c:forEach var="i" begin="1" end="10">
    <span>${i} </span>
</c:forEach>

<!-- 0から100まで10刻み -->
<c:forEach var="i" begin="0" end="100" step="10">
    <option value="${i}">${i}</option>
</c:forEach>

5.6 c:forTokens - 文字列の分割

<c:set var="csvData" value="Java,Python,JavaScript,Go" />
<ul>
    <c:forTokens var="lang" items="${csvData}" delims=",">
        <li>${lang}</li>
    </c:forTokens>
</ul>

5.7 c:url - URL生成

<!-- コンテキストパス付きのURLを生成 -->
<c:url var="userUrl" value="/users">
    <c:param name="page" value="1" />
    <c:param name="sort" value="name" />
</c:url>
<a href="${userUrl}">ユーザー一覧</a>
<!-- 結果: /WebStudy/users?page=1&sort=name -->

6. JSTLフォーマットタグ

6.1 fmt:formatDate - 日付のフォーマット

Servlet側:

request.setAttribute("now", new java.util.Date());
request.setAttribute("timestamp", java.sql.Timestamp.valueOf("2025-04-01 09:30:00"));

JSP側:

<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>

<!-- 日付フォーマット -->
<p>日付: <fmt:formatDate value="${now}" pattern="yyyy年MM月dd日" /></p>
<p>日時: <fmt:formatDate value="${now}" pattern="yyyy/MM/dd HH:mm:ss" /></p>
<p>曜日付き: <fmt:formatDate value="${now}" pattern="yyyy年MM月dd日(E)" /></p>

<!-- typeを指定するパターン -->
<p>日付のみ: <fmt:formatDate value="${now}" type="date" dateStyle="long" /></p>
<p>時刻のみ: <fmt:formatDate value="${now}" type="time" timeStyle="medium" /></p>
<p>日時両方: <fmt:formatDate value="${now}" type="both" /></p>

6.2 fmt:formatNumber - 数値のフォーマット

<c:set var="price" value="1234567" />
<c:set var="rate" value="0.085" />
<c:set var="score" value="78.5" />

<!-- カンマ区切り -->
<p>価格: <fmt:formatNumber value="${price}" pattern="#,###" /></p>
<!-- 出力: 価格: 1,234,567円 -->

<!-- 通貨 -->
<p>通貨: <fmt:formatNumber value="${price}" type="currency" currencySymbol="¥" /></p>
<!-- 出力: 通貨: ¥1,234,567 -->

<!-- パーセント -->
<p>税率: <fmt:formatNumber value="${rate}" type="percent" /></p>
<!-- 出力: 税率: 9% -->

<!-- 小数点以下の桁数指定 -->
<p>スコア: <fmt:formatNumber value="${score}" pattern="#.00" /></p>
<!-- 出力: スコア: 78.50 -->

フォーマットタグまとめ

タグ 用途
fmt:formatDate 日付を指定形式で表示 yyyy年MM月dd日
fmt:formatNumber 数値を指定形式で表示 #,###
fmt:parseDate 文字列を日付に変換 -
fmt:parseNumber 文字列を数値に変換 -

7. JSTL関数タグ

<%@ taglib prefix="fn" uri="jakarta.tags.functions" %>

<c:set var="text" value="  Hello, World!  " />

<p>長さ: ${fn:length(text)}</p>
<p>大文字: ${fn:toUpperCase(text)}</p>
<p>小文字: ${fn:toLowerCase(text)}</p>
<p>トリム: [${fn:trim(text)}]</p>
<p>含む?: ${fn:contains(text, 'World')}</p>
<p>置換: ${fn:replace(text, 'World', 'Java')}</p>
<p>部分文字列: ${fn:substring(text, 2, 7)}</p>

<!-- リストの長さ -->
<p>ユーザー数: ${fn:length(users)}</p>
関数 説明
fn:length(obj) 文字列の長さ / コレクションのサイズ
fn:toUpperCase(str) 大文字に変換
fn:toLowerCase(str) 小文字に変換
fn:trim(str) 前後の空白を除去
fn:contains(str, sub) 部分文字列を含むか
fn:startsWith(str, prefix) 指定文字列で始まるか
fn:endsWith(str, suffix) 指定文字列で終わるか
fn:replace(str, before, after) 文字列の置換
fn:substring(str, begin, end) 部分文字列の取得
fn:split(str, delim) 文字列の分割
fn:join(array, delim) 配列の結合

8. リファクタリング例

Before:スクリプトレット版(ユーザー一覧)

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.List" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="model.User" %>
<%
    List<User> users = (List<User>) request.getAttribute("users");
    String keyword = (String) request.getAttribute("keyword");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
%>
<!DOCTYPE html>
<html>
<body>
    <h1>ユーザー一覧</h1>

    <% if (keyword != null && !keyword.isEmpty()) { %>
        <p>検索キーワード: <%= keyword %></p>
    <% } %>

    <% if (users != null && users.size() > 0) { %>
        <table border="1">
            <tr><th>#</th><th>名前</th><th>年齢</th><th>メール</th><th>登録日</th><th>区分</th></tr>
            <%
                int count = 0;
                for (User user : users) {
                    count++;
            %>
            <tr>
                <td><%= count %></td>
                <td><%= user.getName() %></td>
                <td><%= user.getAge() %></td>
                <td><%= user.getEmail() %></td>
                <td><%= sdf.format(user.getCreatedAt()) %></td>
                <td>
                    <% if (user.getAge() < 20) { %>
                        未成年
                    <% } else if (user.getAge() < 65) { %>
                        一般
                    <% } else { %>
                        シニア
                    <% } %>
                </td>
            </tr>
            <% } %>
        </table>
        <p>合計: <%= users.size() %></p>
    <% } else { %>
        <p>ユーザーが見つかりませんでした。</p>
    <% } %>
</body>
</html>

After:EL式+JSTL版

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
<%@ taglib prefix="fn" uri="jakarta.tags.functions" %>
<!DOCTYPE html>
<html>
<body>
    <h1>ユーザー一覧</h1>

    <c:if test="${not empty keyword}">
        <p>検索キーワード: <c:out value="${keyword}" /></p>
    </c:if>

    <c:choose>
        <c:when test="${not empty users}">
            <table border="1">
                <tr><th>#</th><th>名前</th><th>年齢</th><th>メール</th><th>登録日</th><th>区分</th></tr>
                <c:forEach var="user" items="${users}" varStatus="status">
                <tr>
                    <td>${status.count}</td>
                    <td><c:out value="${user.name}" /></td>
                    <td>${user.age}</td>
                    <td><c:out value="${user.email}" /></td>
                    <td><fmt:formatDate value="${user.createdAt}" pattern="yyyy/MM/dd" /></td>
                    <td>
                        <c:choose>
                            <c:when test="${user.age < 20}">未成年</c:when>
                            <c:when test="${user.age < 65}">一般</c:when>
                            <c:otherwise>シニア</c:otherwise>
                        </c:choose>
                    </td>
                </tr>
                </c:forEach>
            </table>
            <p>合計: ${fn:length(users)}件</p>
        </c:when>
        <c:otherwise>
            <p>ユーザーが見つかりませんでした。</p>
        </c:otherwise>
    </c:choose>
</body>
</html>

比較

項目 スクリプトレット版 EL+JSTL版
import文 3つ必要 不要
Javaコード 大量 なし
可読性 低い 高い
XSS対策 なし c:out で対策あり
日付フォーマット SimpleDateFormat が必要 fmt:formatDate で簡単

練習問題

問題1:EL式で商品表示 ⭐

以下のServletからフォワードされたデータを、EL式を使ってJSPで表示してください。

Servlet側(すでに実装済みとする):

Map<String, Object> product = new HashMap<>();
product.put("name", "ノートPC");
product.put("price", 98000);
product.put("stock", 15);
product.put("onSale", true);
request.setAttribute("product", product);

表示要件:

  • 商品名、価格(カンマ区切り)、在庫数を表示
  • セール中の場合「セール中!」と表示
  • 在庫が5個以下なら「残りわずか」と表示
模範解答
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
<!DOCTYPE html>
<html>
<head><title>商品詳細</title></head>
<body>
    <h1>商品詳細</h1>

    <table border="1">
        <tr>
            <th>商品名</th>
            <td><c:out value="${product.name}" /></td>
        </tr>
        <tr>
            <th>価格</th>
            <td><fmt:formatNumber value="${product.price}" pattern="#,###" /></td>
        </tr>
        <tr>
            <th>在庫</th>
            <td>
                ${product.stock}個
                <c:if test="${product.stock <= 5}">
                    <span style="color: red; font-weight: bold;">残りわずか</span>
                </c:if>
            </td>
        </tr>
        <tr>
            <th>ステータス</th>
            <td>
                <c:if test="${product.onSale}">
                    <span style="color: red; font-size: 1.2em;">セール中!</span>
                </c:if>
                <c:if test="${not product.onSale}">
                    通常販売
                </c:if>
            </td>
        </tr>
    </table>
</body>
</html>

ポイント: Mapのキーに対しては ${product.name} のようにドット記法でアクセスできます。fmt:formatNumber で価格をカンマ区切りにフォーマットしています。

問題2:c:forEach で成績表 ⭐⭐

Servletから以下のデータが渡されます。JSTLを使って成績表を表示し、平均点による評価(A/B/C/D)も表示してください。

Servlet側で渡すデータ:

List<Map<String, Object>> students = new ArrayList<>();

Map<String, Object> s1 = new HashMap<>();
s1.put("name", "田中");
s1.put("japanese", 85);
s1.put("math", 72);
s1.put("english", 90);
students.add(s1);
// ... 同様に複数の生徒を追加

request.setAttribute("students", students);

評価基準:

  • A: 平均90点以上
  • B: 平均70点以上
  • C: 平均50点以上
  • D: 平均50点未満
模範解答
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
<!DOCTYPE html>
<html>
<head>
    <title>成績表</title>
    <style>
        table { border-collapse: collapse; }
        th, td { border: 1px solid #333; padding: 8px; text-align: center; }
        th { background-color: #4CAF50; color: white; }
        .grade-A { color: gold; font-weight: bold; }
        .grade-B { color: blue; }
        .grade-C { color: green; }
        .grade-D { color: red; }
    </style>
</head>
<body>
    <h1>成績表</h1>

    <table>
        <tr>
            <th>#</th>
            <th>名前</th>
            <th>国語</th>
            <th>数学</th>
            <th>英語</th>
            <th>合計</th>
            <th>平均</th>
            <th>評価</th>
        </tr>
        <c:forEach var="student" items="${students}" varStatus="status">
            <c:set var="total" value="${student.japanese + student.math + student.english}" />
            <c:set var="avg" value="${total / 3.0}" />
            <tr>
                <td>${status.count}</td>
                <td><c:out value="${student.name}" /></td>
                <td>${student.japanese}</td>
                <td>${student.math}</td>
                <td>${student.english}</td>
                <td>${total}</td>
                <td><fmt:formatNumber value="${avg}" pattern="#.0" /></td>
                <td>
                    <c:choose>
                        <c:when test="${avg >= 90}">
                            <span class="grade-A">A</span>
                        </c:when>
                        <c:when test="${avg >= 70}">
                            <span class="grade-B">B</span>
                        </c:when>
                        <c:when test="${avg >= 50}">
                            <span class="grade-C">C</span>
                        </c:when>
                        <c:otherwise>
                            <span class="grade-D">D</span>
                        </c:otherwise>
                    </c:choose>
                </td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

ポイント: c:set でEL式の計算結果を変数に格納できます。c:choose/c:when/c:otherwise でif-else if-else相当の条件分岐を実現しています。fmt:formatNumber で小数点以下1桁に揃えています。

問題3:一覧 + 検索 + ページング表示 ⭐⭐

Servletから商品リストと検索条件が渡されるとします。以下の要件でJSPを作成してください。

  • 検索フォーム(キーワード入力 + 検索ボタン)
  • 商品をテーブルで一覧表示
  • 価格が10,000円以上の商品は太字で表示
  • 在庫が0の商品は「売り切れ」と赤字で表示
  • リストが空の場合は「該当する商品がありません」と表示
  • 検索結果の件数を表示
模範解答
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
<%@ taglib prefix="fn" uri="jakarta.tags.functions" %>
<!DOCTYPE html>
<html>
<head>
    <title>商品検索</title>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        .search-form { margin-bottom: 20px; padding: 15px; background: #f0f0f0; border-radius: 5px; }
        table { border-collapse: collapse; width: 100%; max-width: 900px; }
        th, td { border: 1px solid #ddd; padding: 10px; }
        th { background-color: #4CAF50; color: white; }
        tr:nth-child(even) { background-color: #f9f9f9; }
        .expensive { font-weight: bold; }
        .sold-out { color: red; font-weight: bold; }
        .result-count { margin: 10px 0; color: #666; }
    </style>
</head>
<body>
    <h1>商品検索</h1>

    <!-- 検索フォーム -->
    <div class="search-form">
        <form action="${pageContext.request.contextPath}/products/search" method="get">
            <label>キーワード:</label>
            <input type="text" name="keyword" value="${fn:escapeXml(param.keyword)}" placeholder="商品名を入力">
            <button type="submit">検索</button>
        </form>
    </div>

    <!-- 検索条件の表示 -->
    <c:if test="${not empty keyword}">
        <p class="result-count"><c:out value="${keyword}" />」の検索結果: ${fn:length(products)}件
        </p>
    </c:if>

    <!-- 商品リスト -->
    <c:choose>
        <c:when test="${not empty products}">
            <table>
                <tr>
                    <th>#</th>
                    <th>商品名</th>
                    <th>価格</th>
                    <th>在庫</th>
                    <th>ステータス</th>
                </tr>
                <c:forEach var="product" items="${products}" varStatus="status">
                    <tr>
                        <td>${status.count}</td>
                        <td>
                            <c:choose>
                                <c:when test="${product.price >= 10000}">
                                    <span class="expensive"><c:out value="${product.name}" /></span>
                                </c:when>
                                <c:otherwise>
                                    <c:out value="${product.name}" />
                                </c:otherwise>
                            </c:choose>
                        </td>
                        <td>
                            <c:choose>
                                <c:when test="${product.price >= 10000}">
                                    <span class="expensive">
                                        <fmt:formatNumber value="${product.price}" pattern="#,###" /></span>
                                </c:when>
                                <c:otherwise>
                                    <fmt:formatNumber value="${product.price}" pattern="#,###" /></c:otherwise>
                            </c:choose>
                        </td>
                        <td>
                            <c:choose>
                                <c:when test="${product.stock == 0}">
                                    <span class="sold-out">売り切れ</span>
                                </c:when>
                                <c:when test="${product.stock <= 5}">
                                    <span style="color: orange;">${product.stock}個(残りわずか)</span>
                                </c:when>
                                <c:otherwise>
                                    ${product.stock}個
                                </c:otherwise>
                            </c:choose>
                        </td>
                        <td>
                            <c:choose>
                                <c:when test="${product.stock == 0}">販売終了</c:when>
                                <c:when test="${product.onSale}">セール中</c:when>
                                <c:otherwise>通常販売</c:otherwise>
                            </c:choose>
                        </td>
                    </tr>
                </c:forEach>
            </table>
        </c:when>
        <c:otherwise>
            <p>該当する商品がありません。</p>
        </c:otherwise>
    </c:choose>
</body>
</html>

ポイント: fn:escapeXml() で入力値をエスケープしてフォームのvalue属性に安全にセットしています。c:choose のネストで複雑な条件分岐も綺麗に書けます。スクリプトレットを一切使わずに、すべてのロジックをJSTLで表現できています。


まとめ

学んだこと キーワード
スクリプトレットの問題 可読性低下、XSSリスク、非推奨
EL式の基本 ${expression}、演算子、暗黙オブジェクト
データアクセス Bean プロパティ、Map、List、スコープ検索順
JSTLコアタグ c:outc:ifc:choosec:forEachc:set
JSTLフォーマット fmt:formatDatefmt:formatNumber
JSTL関数 fn:lengthfn:containsfn:escapeXml
リファクタリング スクリプトレット → EL式+JSTL

次回は フィルターとリスナー を学びます!


シリーズ一覧:Servlet/JSP入門

  1. 環境構築とはじめてのServlet
  2. HTTPリクエストとレスポンス
  3. JSPの基礎
  4. フォーム処理(GET/POST)
  5. セッション管理とCookie
  6. MVCパターン(Servlet + JSP)
  7. JDBC連携(データベース操作)
  8. 👉 EL式とJSTL(本記事)
  9. フィルターとリスナー
  10. 総合演習:掲示板アプリを作ろう

著者: @kotaro_ai_lab
AI駆動開発やテック情報を毎日発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?