今回は、StrutsのActionFormにList型や配列型のプロパティを用意する方法を説明します。
これを使用すると、繰り返しの入力フィールドを表示することができます。
2種類の方法を紹介します。1つは配列を使う方法で、もう1つはListを使う方法です。
配列を使ったほうが、構造はシンプルですが、Listを使ったほうが使い勝手がよいです。
サンプルソースはこちらです。
kenkyu05.zip
[PR]
Strutsに関しては、この本が、詳細かつ実践的な内容で解説しています。
リンク先のAmazonの評価を見ると分かりますが、だれもが「わかりやすい」と感じ、良い評価を集めているようです。
StrutsによるWebアプリケーションスーパーサンプル
Amazonでは分かりやすいと評判です。
第2版が出版されています。StrutsによるWebアプリケーションスーパーサンプル第2版
配列を使う方法
ActionFormに配列型のプロパティを用意します。配列の1要素が、1明細のプロパティになります。
サンプルプログラムのArrayFormでは、次のように配列型のプロパティを用意しています。
private String[] detailField1;
private String[] detailField2;
これで、n行2列をあらわすことができます。
図にするとこんなイメージになります。
setterとgetterは、配列をセットするsetterと、配列を取得するgetterを作成すればOKです。インデックスを使ったアクセッサメソッドは不要です。
public String[] getDetailField1()
public void setDetailField1(String[] detailField1)
Actionでは、配列を作成してセットしておけばOKです。配列型なので固定長になりますが、内部処理としてはListで作成し、最後にtoArrayしてもよいと思います。このあたりが配列を使った場合の使い勝手の悪さです。
JSPでは、
タグで繰り返して表示します。配列の要素数ぶん繰り返します。
textタグのvalue属性にELを使って値を設定しています。
ちょっと話はそれますが、forEachタグのvarStatus属性は、ループカウンタ的なものを保持しているオブジェクトです。
varStatusで指定した変数名sを使用して、s.index
と書くと、0から始まるループカウントとして使えます。
varStatusは他にも次のようなプロパテイを持っています。
プロパティ | 説明 |
---|---|
current | 現在の要素 |
index | 0から始まるインデックス |
count | 1から始まる要素の位置 |
first | 最初の要素かどうか(boolean) |
last | 最後の要素かどうか(boolean) |
begin | forEachタグのbegin属性の値 |
end | forEachタグのend属性の値 |
step | forEachタグのstep属性の値 |
JSP2.0のELの場合は、textタグのvalue属性にELで記述することができますが、JSP1.2の場合は、これができません。
その場合には、通常のHTMLタグであるタグで、記述するしかないと思います。
(他のうまい方法があったら教えてください)
これでvalue属性に対して、
タグで記述すれば、OKでした。JSP1.2のJSTLの場合はこのような記述になります。
"/>
タグの中にタグがあるので、ちょっと気持ち悪いです。
Listを使う方法
ActionFormにList型のプロパティを用意します。このListには1行分の情報をあらわすJavaBeanを格納します。
サンプルプログラムListFormでは、インナークラスDetailというクラスが、1行分の情報をあらわすJavaBeanとなります。
private List detailList = new ArrayList();
ArrayFormのときと同じようにn行2列にするために、Detailクラスは2つのフィールドdetailField1と、detailField2を持ちます。
このListに3つのDetailオブジェクトを格納した場合は、3行分の明細を持つという意味になります。
図にしてあらわすと、このようになります。
インナークラスである必要性はありませんが、ActionForm内の1明細をあらわすオブジェクトなので、不要にソースファイルを増やすよりもインナークラスで十分かと判断しました。
またstaticなインナークラスでもよいのですが、これまたDetailオブジェクト単独で存在することは想定しないので、staticにはしませんでした。このことにより、Detailオブジェクトを生成するためには、必ずListFormのインスタンスが必要になります。
Detail detail = listForm.new Detail(); //インスタンス.newという記述方法
getterとsetterは、通常のフィールドと同じように、Listプロパティに対するgetterとsetterを作成します。インナークラスDetailの各フィールドにも、getter,setterを用意します。
そして最も重要なアクセッサメソッドであるgetDetailを作成します。これは、List内の1要素を取得するメソッドです。ListにはDetailオブジェクトをn個格納するので、インデックスを引数に、Detailオブジェクトを1つ取得できるメソッドを用意するわけです。
このメソッドでは、なにやらややこしいことを最初にしていますが、この処理の説明はあとでします。
基本的には、
return (Detail)detailList.get(index);
これが全てです。
Actionクラスの処理は、ArrayActionと比べてシンプルです。Listを使っているので、可変個数のデータを格納できます。
たとえば、DBから検索した結果が何件になるか分からなくても、1レコード得るごとに、Detailオブジェクトを生成して、Listにaddすればよいだけです。下記は、10明細をdetailListにaddしている例です。
List detailList = new ArrayList();
for (int i = 0; i < 10; i++){
Detail detail = listForm.new Detail();
detail.setDetailField1("明細1-" + i);
detail.setDetailField2("明細2-" + i);
detailList.add(detail);
}
listForm.setDetailList(detailList);
JSPの記述も、ちょっとしたコツを守れば、配列を使った時よりもシンプルになります。
コツは次の2点です。
- forEachタグのループ変数varの名前を
detail
にします。
これは、ListFormの1要素を得るためのアクセッサメソッドが、getDetailで作ったからです。 - 明細部のフィールド全てに対して、
name="detail" indexed="true"
にします。
List内の1要素DetailのdetailField1プロパティを取得しつつ、明細データという意味でindexedをtrueに設定します
こうすることで、めでたくList内の明細データがHTMLに表示されます。テキストフィールドの出力結果は次のようになります。
indexed="true"とすると、name属性にインデックス[1]が付加されるようになります。これをsubmitするとStrutsは都合よくActionFormに格納しようとします。
Strutsのこの明細形式のリクエストデータを受け取ると、まずActionFormを生成します。これはListFormをnewすることになります。
次に、detail[1]
の部分から、getDetail(1)を実行し、Detailオブジェクトを得ます。
そして、得られたDetailオブジェクトのdetailField1の値に、value値"abcdefg"をセットします。
listForm.getDetail(1).setDetailField1("abcdefg");
このような処理が行われるわけです。 name="detail[1].detailField1" value="abcdefg"
にはそのような意味が含まれています。
ところが、ListFormをnewした直後に、getDetail(1)としても、detailListは空っぽなので、このままではListの要素数を超えたアクセスによる例外(ArrayIndexOutOfBoundsException)が発生しかねません。
それを避ける処理が、ListFormのgetDetailで書いてあった処理です。
if (detailList.size() - 1 < index){
while(detailList.size() - 1 < index){
detailList.add(new Detail());
}
}
これは、Listの要素数を超えたアクセスをされた時に、要素数分のDetailオブジェクトをその場で追加してしまう処理です。
ちょっと乱暴な気がしますが、これによりsubmitされてきた件数に合わせて、Detailオブジェクトがnewされるようになります。
まとめ
オススメするのは、List型のプロパティを使う方法です。とても扱いやすく便利だと思います。
List型のプロパティを使う際のポイントは次の5つです。
- 明細の1要素をあらわすクラスを作成する
- 明細の1要素へのアクセッサメソッドを用意する
- 要素数を超えたアクセスに対する処理を記述する
- JSPのループ変数名に、1要素をあらわすプロパティを使用する
- Strutsのhtml:タグで、indexed属性をtrueに設定する
以前お騒がせした、初心者のDOONです。
いまや、中級者のなりました(冗談です)
scope=”request”なので、
このActionFormにListで作成して、表示したあとにですね、
画面からhtml:textに入力して、その値をsubmitで取得するとします。
その時ActionFormのsetterメソッドが走って、画面の入力値を設定すると思います。
そのときListはnew ArrayListしかしていないので、
要素数OVERが起きるのではないでしょうか?
でも、起きないのは、なぜでしょう?
setterにもListの要素数拡大が必要な気がしています。
もしよろしければ、教えてください。
getDetailメソッドで、要素追加しているからです。
うーん、私がバカですね。すいません。
もう一度聞きます。もしよろしければ、教えてください。
jspを表示させたら、scope=requestなんで、ActionFormはなくなりますよね。
そのあとに、submitしたら、新しいActionFormができて、そのhtml:textのsetterメソッドが
先に動くのではないでしょうか?
その前にgetDetailが動くのは、なぜでしょうか?
JSPで出力した結果を”ブラウザの”HTMLソースを表示して見てみてください。
<input name=”detail[1].detailField1″ value=”abcdefg” type=”text” />
こんな感じになっています。あとは記事にも書いてある通りなんですが、
これをサーバーへsubmitすると、
detail[1].detailField1=abcdefg
という情報が送信されます。Strutsはこれを受け取ると、
listForm.getDetail(1).setDetailField1(“abcdefg”);
というのと同じような処理を行います。つまりlistFormからgetDetailを実行し要素の1個目を取得しようとします。
>html:textのsetterメソッドが先に動くのではないでしょうか?その前にgetDetailが動くのは、なぜでしょうか?
という疑問点については、
set先がActionFormではなく、ActionFormの中のListの中のn番目のBeanにsetするという指定になっているからです。
テキストフィールドに入力した値をどこにセットしていると思いますか?ActionFormにsetしているのではありません。
ActionFormからListをgetして、Listからn番目の要素をgetして(これがgetDetail)、getしたBeanに対して値をセットしているのです。なので値をセットするためには、先にgetDetailで取得する対象となるBeanをgetしなければならないのです。
わかりやすい解説、痛み入ります。
ありがとうございました。
いつもの通り、もう少し勉強します。いつもすいません。
参考にしているのですが、高度すぎて、いつも挫折しています。
また、よろしくお願いします。
初心者にもどります。