OGNLとs:pushの組み合わせでハッピーStruts2ライフ
Struts2でWebアプリを作る際に、JSPを部品化したいと思って以下のような「ユーザー表示」テンプレートを作ったとする。
<table> <tr><td>お名前</td><td><s:property value="user.name" /></td></tr> <tr><td>メールアドレス</td><td><s:property value="user.mail" /></td></tr> </table>
これを表示するときに、うまくAction側で user というプロパティ名にできればいいんだけど、同じページにユーザーをふたり表示させたくて
public class MyAction { private User user1; private User user2; public User getUser1() { return user1; } public User getUser2() { return user2; } ... }
としただけで、このテンプレートはそのまま使えなくなる。
user なんて名前を含めるからいけないんじゃんということで
<table> <tr><td>お名前</td><td><s:property value="name" /></td></tr> <tr><td>メールアドレス</td><td><s:property value="mail" /></td></tr> </table>
として、テンプレートを使う側で
<s:push value="user1"> <jsp:include page="user.jsp" /> </s:push> <s:push value="user2"> <jsp:include page="user.jsp" /> </s:push>
とするといちおうはOKだけど、元々のテンプレートでは user が null でもナアナアで済ませて(s:property の出力が全て空になるだけ)くれたのに対して、こちらの場合は user1 や user2 が null だと s:push が問答無用でエラーを吐いてしまう。OGNL的にはnullがいくらあっても問題ないけど、ValueStack自体にnullを積むのはダメ絶対!というわけだ。
これに対する簡単で柔軟性も維持できる解決策としては、こんなのしか思いつかなかった。
<s:push value="#{'user':user1}"> <jsp:include page="user.jsp" /> </s:push> <s:push value="#{'user':user2}"> <jsp:include page="user.jsp" /> </s:push>
見ての通り、userというキーを持つMapをその場でValueStackに積んでいる。テンプレートは最初の user という名前で参照する方を使う。これなら user1 や user2 が null でも問題ない。
この手法は他にも利点があって、たとえば user 以外にも何かテンプレートに対してパラメータを渡したいような場合、Mapに追加のキーを値を設定すればお手軽に設定できる。
<table> <tr><td>お名前</td><td><s:property value="user.name" /></td></tr> <tr><td>メールアドレス</td><td><s:property value="user.mail" /></td></tr> <s:if test="showAge"><tr><td>年齢</td><td><s:property value="user.age" /></td></tr></s:if> </table>
<s:push value="#{'user':user1, 'showAge':false}"> <jsp:include page="user.jsp" /> </s:push> <s:push value="#{'user':user2, 'showAge':true}"> <jsp:include page="user.jsp" /> </s:push>
OGNLって気持ち悪いけど、一旦魂を売り渡してしまうと楽しくなってくるね。