Strutsでは、テーブルの行とか箇条書きとかを不定回数繰り返して処理するために、logic:iterateタグを使う。
1つのデータだけを繰り返す例。
package jp.hishidama.sample.struts.iterate1; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionMapping; @SuppressWarnings("serial") public class Iterate1Form extends ActionForm { protected List<String> nameList; public void setNameList(List<String> list) { nameList = list; } public List<String> getNameList() { return nameList; } }
package jp.hishidama.sample.struts.iterate1;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class Iterate1InitAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Iterate1Form initBean = new Iterate1Form();
request.setAttribute("iterate1Form", initBean);
// ループするデータの初期化
List<String> list = new ArrayList<String>();
list.add("name1");
list.add("name02");
list.add("name-3");
initBean.setNameList(list);
return mapping.findForward("success");
}
}
<%@ page contentType="text/html; charset=Windows-31J"%> <%@taglib uri="/struts-bean" prefix="bean"%> <%@taglib uri="/struts-html" prefix="html"%> <%@taglib uri="/struts-logic" prefix="logic"%> <html:html> <head> <title>struts logic:iterate sample1</title> </head> <body> <html:form action="/iterate1submit"> <table border="1"> <logic:iterate id="loop" name="iterate1Form" property="nameList"> <tr> <td><bean:write name="loop" /></td> </tr> </logic:iterate> </table> </html:form> </body> </html:html>
logic:iterateのname属性には、Actionでセットしたフォーム名を指定する。
property属性には、そのフォームのプロパティー名を指定する。そのプロパティーの型は、ここではList<String>になっている。
id属性で指定した名前で、ループ毎の値が取れるようになるので、bean:writeのname属性にその名前を指定すればいい。
また、logic:iterateにindexId属性を指定すると、ループカウンター(0から始まる値)がその名前で取れるようになる。
<table border="1"><logic:iterate id="loop" name="iterate1Form" property="nameList" indexId="idx"> <tr> <td><bean:write name="idx" /></td> <td><bean:write name="loop" /></td> </tr></logic:iterate> </table>
<table border="1"> <tr> <td>0</td> <td>name1</td> </tr> <tr> <td>1</td> <td>name02</td> </tr> <tr> <td>2</td> <td>name-3</td> </tr> </table>
普通は、一行に組となるデータがいくつもあることがほとんどだと思う。
この場合、一行分に相当するサブフォーム(JavaBean)を別途定義しておき、フォームにはそのリストを指定する。
public Iterate2LoopBean { protected String id; protected String name; public void setId(String s) { id = s; } public String getId() { return id; } public void setName(String s) { name = s; } public String getName() { return name; } }
@SuppressWarnings("serial") public Iterate2Form extends ActionForm { protected List<Iterate2LoopBean> loopList; public void setLoopList(List<Iterate2LoopBean> list) { loopList = list; } public List<Iterate2LoopBean> getoopList() { return loopList; } }
public class Iterate2InitAction extends Action { @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { Iterate2Form initBean = new Iterate2Form(); request.setAttribute("iterate2Form", initBean); // ループするデータの初期化 List<Iterate2LoopBean> list = new ArrayList<Iterate2LoopBean>(); Iterate2LoopBean bean1 = new Iterate2LoopBean(); bean1.setId("001"); bean1.setName("name1"); list.add(bean1); 〜 initBean.setLoopList(list); return mapping.findForward("success"); } }
<%@ page contentType="text/html; charset=Windows-31J"%> <%@taglib uri="/struts-bean" prefix="bean"%> <%@taglib uri="/struts-html" prefix="html"%> <%@taglib uri="/struts-logic" prefix="logic"%> <html:html> <head> <title>struts logic:iterate sample2</title> </head> <body> <html:form action="/iterate2submit"> <table border="1"> <logic:iterate id="loop" name="iterate2Form" property="loopList"> <tr> <td><bean:write name="loop" property="id" /></td> <td><bean:write name="loop" property="name" /></td> </tr> </logic:iterate> </table> </html:form> </body> </html:html>
iterate2FormのgetLoopList()で取れるのはIterate2LoopBeanのリストなので、id="loop"(bean:writeのname="loop")で取れるものはIterate2LoopBeanの1つのインスタンスとなる。
なので、bean:writeのpropertyでIterate2LoopBeanのプロパティー名を指定すれば、個々の値を取ることが出来る。
値を出力するのみでなく、input type="text"の場合も、同様にJavaBeanに入れておけばよい。
ただし、サブミットされた際にフォームインスタンスに値をセットするためのメソッドが必要になる。
package jp.hishidama.sample.struts.iterate3; public class Iterate3LoopBean { protected String id; // IDの出力用 protected String name; // 名前の出力用 protected String text; // テキストの入出力用 〜id,nameのセッター・ゲッター〜 public void setText(String s) { text = s; } public String getText() { return text; } }
package jp.hishidama.sample.struts.iterate3; import java.util.ArrayList; import java.util.List; import org.apache.struts.action.ActionForm; @SuppressWarnings("serial") public class Iterate3Form extends ActionForm { protected List<Iterate3LoopBean> loopList; public void setLoopList(List<Iterate3LoopBean> loopList) { this.loopList = loopList; } public List<Iterate3LoopBean> getLoopList() { return loopList; } // サブミットされた時に、当フォームへ値をセットする為に使用される public Iterate3LoopBean getLoop(int index) { if (loopList == null) { //リストが無い場合は作る loopList = new ArrayList<Iterate3LoopBean>(); } for (int i = loopList.size(); i <= index; i++) { loopList.add(null); //足りないデータは空で作っておく } Iterate3LoopBean bean = loopList.get(index); if (bean == null) { bean = new Iterate3LoopBean(); loopList.set(index, bean); } return bean; } }
Iterate2InitActionと同様(textの初期化を加える)。
<%@ page contentType="text/html; charset=Windows-31J"%> <%@taglib uri="/struts-bean" prefix="bean"%> <%@taglib uri="/struts-html" prefix="html"%> <%@taglib uri="/struts-logic" prefix="logic"%> <html:html> <head> <title>struts logic:iterate3</title> </head> <body> <html:form action="/iterate3submit"> <h1>logic:iterate sample3</h1> <table border="1"> <logic:iterate id="loop" name="iterate3Form" property="loopList" indexId="idx"> <tr> <td><bean:write name="loop" property="id" /></td> <td><bean:write name="loop" property="name" /></td> <td><html:text name="loop" property="text" indexed="true" /></td> </tr> </logic:iterate> </table> <p><html:submit value="サブミットボタン" /></p> </html:form> </body> </html:html>
<html> <head> <title>struts logic:iterate3</title> </head> <body> <form name="iterate3Form" method="post" action="/contextroot/iterate3submit.do"> <h1>logic:iterate sample3</h1> <table border="1"> <tr> <td>01</td> <td>name1</td> <td><input type="text" name="loop[0].text" value=""></td> </tr> <tr> <td>02</td> <td>name2</td> <td><input type="text" name="loop[1].text" value=""></td> </tr> <tr> <td>03</td> <td>name-3</td> <td><input type="text" name="loop[2].text" value=""></td> </tr> </table> <p><input type="submit" value="サブミットボタン"></p> </form> </body> </html>
生成されたinput type="text"タグの名前に注目。
html:linkのindexed属性にtrueを指定したことで、「html:linkのname属性の値」+"[
"+インデックス+"].
"+「html:linkのproperty属性の値」という形式の名前で生成される。
したがって、サブミット時にセットされるフォームクラス(Iterate3Form)には、この形式の名前を受け付けるようなメソッドを用意しておく必要がある。
(name[index].property
という形式の名前に対しては、まずForm#getName(index)が呼ばれる。そこで返ったBeanに対し、setProperty(値)が呼ばれる)
フォームを使うAction側は、普通どおり。
public class Iterate3SubmitAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Iterate3Form bean = (Iterate3Form) form;
List<Iterate3LoopBean> list = bean.getLoopList();
for (int i = 0; i < list.size(); i++) {
Iterate3LoopBean loop = list.get(i);
System.out.printf("Iterate3SubmitAction#loopList[%d]#text: %s%n", i, loop.getText());
}
return mapping.findForward("success");
}
}
行毎にサブミットボタンを用意し、それが押された際にどの行のボタンだったのかを知りたい場合、以下のように出来る。
<html:form action="/iterate4submit"> <h1>logic:iterate sample4</h1> <table border="1"> <logic:iterate id="loop" name="iterate4Form" property="loopList"> <tr> <td><bean:write name="loop" property="id" /></td> <td><bean:write name="loop" property="name" /></td> <td><html:submit property="loopButton" indexed="true" value="行毎のボタン" /> </tr> </logic:iterate> </table> <p><html:submit value="サブミットボタン" /></p> </html:form>
html:submitのproperty名は、フォームから値を取ってくるわけでは無いので、他とかぶらない名前を好きに付けていい。
ここで付けた名前でsubmitボタンの名前が生成される。
<form name="iterate4Form" method="post" action="/contextroot/iterate4submit.do"> <h1>logic:iterate sample4</h1> <table border="1"> <tr> <td>01</td> <td>name1</td> <td><input type="submit" name="loopButton[0]" value="行毎のボタン"> </tr> <tr> <td>02</td> <td>name2</td> <td><input type="submit" name="loopButton[1]" value="行毎のボタン"> </tr> <tr> <td>03</td> <td>name-3</td> <td><input type="submit" name="loopButton[2]" value="行毎のボタン"> </tr> </table> <p><input type="submit" value="サブミットボタン"></p> </form>
サブミットボタンの場合、押されたボタンのnameとvalueだけがリクエストに入ってくる。
つまり、一番上のサブミットボタンを押した場合、「loopButton[0]=行毎のボタン
」というリクエストパラメーターになり、loopButton[1]や[2]は送られて来ない。
(一番下のループ外のサブミットボタンには名前が付いていないので、リクエストパラメーターとしては何も送信されない)
したがって、フォームのセッターは以下のようにコーディングしておけばいい。(ゲッターは使わない)
@SuppressWarnings("serial") public class Iterate4Form extends ActionForm { protected List<Iterate4LoopBean> loopList; /** 押されたサブミットボタンの番号 */ protected int loopButtonNo = -1; 〜loopListのセッター・ゲッター〜 //リクエストからセットされる時に呼ばれる public void setLoopButton(int index, String value) { loopButtonNo = index; } //ゲッターは無くても大丈夫そう public String getLoopButton(int index) { throw new UnsupportedOperationException("index=" + index); } //Actionから呼ばれる public int getLoopButtonNo() { return loopButtonNo; } @Override public void reset(ActionMapping mapping, HttpServletRequest request) { super.reset(mapping, request); loopList = null; loopButtonNo = -1; } }
public class Iterate4SubmitAction extends Action { @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("Iterate4SubmitAction#parameters: " + request.getParameterMap()); //リクエストパラーメーターの確認 Iterate4Form bean = (Iterate4Form) form; System.out.println("ボタン番号:" + bean.getLoopButtonNo()); return mapping.findForward("success"); } }
name="loopButton[1]"のサブミットボタンが押されたとき、フォームのsetLoopButton(1,
〜)が呼ばれる。
この番号をフォーム内に保持しているので、getLoopButtonNo()ではそれが取得できる。
重要なのが、フォームのreset()メソッド!
struts-config.xmlのaction要素のscope属性を省略した場合、
フォーム自体はセッションスコープになるようだ。
つまり、別リクエストであっても同一インスタンスが使われることになる。
この場合、リクエストが発生する度にフィールドを初期化するようにしておかないと、前のリクエストの情報が残ってしまう。
(例えばそれが別ユーザーのリクエストだったとすると、他人の情報を参照/使用してしまうような事態になりかねない)
その際に初期化の為に呼ばれるのがreset()なので、これをオーバーライドしてコーディングしておく必要がある。
サブミットボタンの場合、name属性が付いていないとそのリクエストパラメーター自体作られないので、フォームのセッターメソッドも呼ばれない。
したがって上記の例のようにボタン番号を保持する作りだと、リセットしないと以前の情報が残ってしまう。
ループ毎のサブミットボタンと同様に、ループ毎のリンクを作ることが出来る。
(同一のURIだがパラメーターにインデックス番号を付加させることが出来る)
ただし、リンクはサブミットではないので、フォーム内の他の入力エリアの情報が送信されることは無い。
(JavaScriptが使えるなら、submit()関数でサブミットすればいいのだが)
<html:form action="/iterate5submit"> <h1>logic:iterate sample5</h1> <table border="1"> <logic:iterate id="loop" name="iterate5Form" property="loopList"> <tr> <td><bean:write name="loop" property="id" /></td> <td><bean:write name="loop" property="name" /></td> <td><html:link action="/iterate5link" indexed="true" indexId="linkIndex">リンク</html:link></td> </tr> </logic:iterate> </table> </html:form>
html:linkのindexed属性にtrueを指定すると、生成されるURIにループカウンターのパラメーターが付加される。
パラメーター名はindexId属性で指定する。省略時は「index」になる。
<form name="iterate5Form" method="post" action="/contextroot/iterate5submit.do"> <h1>logic:iterate sample5</h1> <table border="1"> <tr> <td>01</td> <td>name1</td> <td><a href="/contextroot/iterate5link.do?linkIndex=0">リンク</a></td> </tr> <tr> <td>02</td> <td>name2</td> <td><a href="/contextroot/iterate5link.do?linkIndex=1">リンク</a></td> </tr> <tr> <td>03</td> <td>name-3</td> <td><a href="/contextroot/iterate5link.do?linkIndex=2">リンク</a></td> </tr> </table> </form>
@SuppressWarnings("serial") public class Iterate5Form extends ActionForm { protected List<Iterate5LoopBean> loopList; protected String linkIndex = null; 〜 public void setLinkIndex(String value) { linkIndex = value; } public String getLinkIndex() { return linkIndex; } @Override public void reset(ActionMapping mapping, HttpServletRequest request) { super.reset(mapping, request); loopList = null; linkIndex = null; } }
リンクの場合は、ループのインデックス番号だけでなく、コード値を使うことも出来る。
<html:form action="/iterate5submit"> <h1>logic:iterate sample5</h1> <table border="1"> <logic:iterate id="loop" name="iterate5Form" property="loopList"> <tr> <td><bean:write name="loop" property="id" /></td> <td><bean:write name="loop" property="name" /></td> <td><html:link action="/iterate5link" paramId="linkIndex" paramName="loop" paramProperty="id">リンク</html:link></td> </tr> </logic:iterate> </table> </html:form>
html:linkタグのparamId属性がリクエストパラメーター名、paramName属性にループのBean名、paramProperty属性にそのBeanのプロパティー名を指定する。
すると、paramIdに指定した名前で、paramName・paramPropertyのプロパティー値がセットされる。
<form name="iterate5Form" method="post" action="/contextroot/iterate5submit.do"> <h1>logic:iterate sample5</h1> <table border="1"> <tr> <td>01</td> <td>name1</td> <td><a href="/contextroot/iterate5link.do?linkIndex=01">リンク</a></td> </tr> <tr> <td>02</td> <td>name2</td> <td><a href="/contextroot/iterate5link.do?linkIndex=02">リンク</a></td> </tr> <tr> <td>03</td> <td>name-3</td> <td><a href="/contextroot/iterate5link.do?linkIndex=03">リンク</a></td> </tr> </table> </form>
一行で複数のデータを扱いたいが(ヘボなフレームワークやコーディング規約の都合とかで)サブフォーム(JavaBean)のリストを使えない場合の方法を考えてみる。
@SuppressWarnings("serial") public class Iterate6Form extends ActionForm { protected List<String> idList; protected List<String> nameList; public void setIdList(List<String> list) { this.idList = list; } public List<String> getIdList() { return idList; } public void setNameList(List<String> list) { this.nameList = list; } public List<String> getNameList() { return nameList; } }
public class Iterate6InitAction extends Action { @Override public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { Iterate6Form initBean = new Iterate6Form(); request.setAttribute("iterate6Form", initBean); List<String> idList = new ArrayList<String>(); idList.add("01"); idList.add("02"); idList.add("03"); initBean.setIdList(idList); List<String> nameList = new ArrayList<String>(); nameList.add("name1"); nameList.add("name2"); nameList.add("name-3"); initBean.setNameList(nameList); return mapping.findForward("success"); } }
<%@ page contentType="text/html; charset=Windows-31J"%> <%@taglib uri="/struts-bean" prefix="bean"%> <%@taglib uri="/struts-html" prefix="html"%> <%@taglib uri="/struts-logic" prefix="logic"%> <html:html> <head> <title>struts logic:iterate6</title> </head> <body> <html:form action="/iterate6submit"> <h1>logic:iterate sample6(複数リスト)</h1> <table border="1"> <logic:iterate id="id" name="iterate6Form" property="idList" indexId="idx"> <tr> <td><bean:write name="id" /></td> <td>???</td> </tr> </logic:iterate> </table> </html:form> </body> </html:html>
まず、iterateはid属性でループの値の変数名を指定できるので、1つ分は普通に扱える。
またindexId属性があるので、カウンターはその値が使える。
とすれば、bean:writeでカウンターの値が指定できればよい。
ただ、html:textとかにはindexed属性があるのだが、bean:writeには無い。
property属性が「nameList[n]
」になればよいので、ひとまずJSP式を使って、以下のようにすれば出来る。
<td><bean:write name="iterate6Form" property='<%= "nameList[" + idx + "]" %>' /></td>
そこだけ属性値をシングルクォーテーションでくくるのも違和感があるが、ダブルクォーテーションを使おうとすると、JSP式内のダブルクォーテーションが属性値の終了扱いになってしまって、エラーになる。
JSP式内のダブルクォーテーションをエスケープしてやれば出来はするが、見栄えが…というより、面倒(苦笑)
<td><bean:write name="iterate6Form" property="<%= \"nameList[\" + idx + \"]\" %>" /></td>
EL式を使っていいなら、もうちょっとシンプルに出来る。
<td><bean:write name="iterate6Form" property="nameList[${idx}]" /></td>
…つーか、EL式を使うなら、そもそもbean:writeを使う必要も無いのだが(爆)
<td>${ iterate6Form.nameList[idx] }</td>
html:textにはindexed属性があるから出来るかと思ったら、そうでもないらしい…。
<td><html:text name="iterate6Form" property="textList" indexed="true" /></td>
List<String> textList = new ArrayList<String>();
textList.add(null);
textList.add("text2");
textList.add(null);
initBean.setTextList(textList);
↓
<td><input type="text" name="iterate6Form[0].textList" value="[null, text2, null]"></td> 〜 <td><input type="text" name="iterate6Form[1].textList" value="[null, text2, null]"></td> 〜 <td><input type="text" name="iterate6Form[2].textList" value="[null, text2, null]"></td>
name属性は「textList[0]
」となって欲しいところだし、value属性の初期値も変だ(爆)
bean:writeの場合と同じく、JSP式やEL式を使えば出来るみたいだけどねぇ。
<td><html:text name="iterate6Form" property="textList[${idx}]" /></td>
サブミットボタンやリンクは特に問題ないようだ。
<td><html:link action="/iterate6submit" indexed="true" indexId="linkIndex">リンク</html:link></td> <td><html:link action="/iterate6submit" paramId="linkIndex" paramName="id">リンク</html:link></td> <td><html:submit property="loopButton" indexed="true" value="ボタン" /></td>