サンプル解説
s2jsf-exmapleのプログラムを順に解説します。
実際に動作するサンプルを参考に、S2JSFの使い方を学習しましょう。
S2JSF1.1に限定されますが、Teeda ExtensionのItemsSave同様の機能を使用する事が可能です。
今まで、ForEachで使用していたListを次画面に引き継ぐには対象Listのinstance属性をsessionにする必要がありましたが
Teeda ExtensionのItemsSave機能を使う事によりinstance属性をrequestにして使用する事が可能です。
使用手順
app.diconにRenderer登録用の以下の記述を追記します
<component
class="org.seasar.teeda.core.render.autoregister.TeedaRendererComponentAutoRegister">
<initMethod name="addReferenceClass">
<arg>@org.seasar.teeda.extension.render.html.HtmlTextRenderer@class</arg>
</initMethod>
<initMethod name="addClassPattern">
<arg>"org.seasar.teeda.extension.render.html"</arg>
<arg>"THtmlItemsSaveHiddenRenderer"</arg>
</initMethod>
</component>
jsf.diconにTaglib登録として以下の記述を追加します
<initMethod name="addTaglibUri">
<arg>"te"</arg>
<arg>"http://www.seasar.org/teeda/extension"</arg>
</initMethod>
これでItemsSave機能自体を使う準備は整いました。
画面で使用するhogeDtoListというDTOのListはdiconファイルに以下のように登録されていると仮定します
※instance属性がrequestになっています
<component name="hogeDtoList" class="java.util.ArrayList" instance="request">
<initMethod name="add" >
<arg>
<component class="examples.jsf.dto.HogeDto" instance="prototype">
<property name="input">"aaa"</property>
</component>
</arg>
</initMethod>
<initMethod name="add">
<arg>
<component class="examples.jsf.dto.HogeDto" instance="prototype">
<property name="input">"bbb"</property>
</component>
</arg>
</initMethod>
</component>
使用する画面のActionにListのgetterを用意します。
/**
* @return hogeDtoList
*/
public List getHogeDtoList() {
return hogeDtoList;
}
実際のHTMLでは以下のように指定します
<span m:inject="te:inputHidden" m:value="#{hogeDtoList}"/>
app.diconでは、
コンポーネントの自動登録機能・
アスペクトの自動登録機能を用いて、
Action・Dto・LogicクラスをS2Containerへ登録しています。Action・Logicへは同時にアスペクトも登録しています。
app.diconでActionをS2Containerへ登録している箇所はこのようになっています。
01:<component
02: class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister"
03:>
04: <property name="instanceDef">
05: @org.seasar.framework.container.deployer.InstanceDefFactory@REQUEST
06: </property>
07: <initMethod name="addClassPattern">
08: <arg>"examples.jsf.action.impl"</arg>
09: <arg>".*ActionImpl"</arg>
10: </initMethod>
11:</component>
12:
13:<component
14: class="org.seasar.framework.container.autoregister.AspectAutoRegister"
15:>
16: <property name="interceptor">actionInterceptorChain</property>
17: <initMethod name="addClassPattern">
18: <arg>"examples.jsf.action.impl"</arg>
19: <arg>".*ActionImpl"</arg>
20: </initMethod>
21:</component>
01~11行目で、名称が"ActionImpl"で終わるクラスをrequestスコープへ登録しています。
これは、Actionクラスは入力された内容をsetter経由で受け取ることになるためです。
component名は、クラス名の先頭1文字を小文字にし、末尾のImplを除いた値になります。
クラス名が"aaa.bbb.FooActionImpl"の場合は"fooAction"がコンポーネント名になります。
13~21行目で、同じく"ActionImpl"で名称が終わるクラスにactionInterceptorChainアスペクトを登録しています。
actionInterceptorChainはexamples/jsf/dicon/allaop.diconで設定されています。
01:<component
02: name="actionThrowsInterceptor"
03: class="examples.jsf.interceptor.ActionThrowsInterceptor"
04:/>
05:
06:<component
07: name="actionInterceptorChain"
08: class="org.seasar.framework.aop.interceptors.InterceptorChain"
09:>
11: <initMethod name="add"><arg>aop.traceInterceptor</arg></initMethod>
12: <initMethod name="add"><arg>actionThrowsInterceptor</arg></initMethod>
13:</component>
org.seasar.framework.aop.interceptors.InterceptorChainは、複数のInterceptorをグルーピング化し、利用しやすくします。
ここではaop.traceInterceptorとactionThrowsInterceptorを1つにまとめています。
1つ目のaop.traceInterceptorはaop.diconで以下のように定義しています。
<component name="traceInterceptor"
class="org.seasar.framework.aop.interceptors.TraceInterceptor"
/>
org.seasar.framework.aop.interceptors.TraceInterceptorは、S2AOPで用意されているInterceptorで、
呼び出しの前後にトレース処理を行うものです。
actionInterceptorChainの2つ目のactionThrowsInterceptorは、
examples/jsf/dicon/allaop.diconで以下のように定義されています。
<component name="actionThrowsInterceptor"
class="examples.jsf.interceptor.ActionThrowsInterceptor"
/>
examples.jsf.interceptor.ActionThrowsInterceptorは独自のThrowsInterceptorで以下のようになっています。
public class ActionThrowsInterceptor extends ThrowsInterceptor {
public String handleThrowable(AppRuntimeException ex, MethodInvocation invocation)throws Throwable{
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, MessageUtil.getErrorMessage(ex.getMessageId(), ex.getArgs()));
return null;
}
}
AppRuntimeExceptionからID/引数を取り出してFacesMessageを構築し、FacesContextへセットします。
このようにエラー処理をInterceptorにまかせれば、Actionの実装はPOJOで作成することができます。
app.diconでDTOをS2Containerへ登録している箇所はこのようになっています。
<component
class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister"
>
<property name="instanceDef">
@org.seasar.framework.container.deployer.InstanceDefFactory@REQUEST
</property>
<initMethod name="addClassPattern">
<arg>"examples.jsf.dto"</arg>
<arg>".*Dto"</arg>
</initMethod>
</component>
名称が"Dto"で終わるクラスをrequestスコープへ登録しています。
app.diconでLogicをS2Containerへ登録している箇所はこのようになっています。
01:<component
02: class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister"
03:>
04: <initMethod name="addClassPattern">
05: <arg>"examples.jsf.logic.impl"</arg>
06: <arg>".*LogicImpl"</arg>
07: </initMethod>
08:</component>
09:
10:<component
11: class="org.seasar.framework.container.autoregister.AspectAutoRegister"
12:>
13: <property name="interceptor">logicInterceptorChain</property>
14: <initMethod name="addClassPattern">
15: <arg>"examples.jsf.logic.impl"</arg>
16: <arg>".*LogicImpl"</arg>
17: </initMethod>
18:</component>
01~08行目で、名称が"LogicImpl"で終わるクラスを登録しています。
インスタンスモードは、明示的に指定していないためデフォルトのsingletonになります。
10~18行目で、同じく"LogicImpl"で名称が終わるクラスにlogicInterceptorChainアスペクトを登録しています。
logicInterceptorChainはexamples/jsf/dicon/allaop.diconで設定されています。
<component
name="logicInterceptorChain"
class="org.seasar.framework.aop.interceptors.InterceptorChain"
>
<initMethod name="add"><arg>aop.traceThrowsInterceptor</arg></initMethod>
<initMethod name="add"><arg>aop.traceInterceptor</arg></initMethod>
<initMethod name="add"><arg>j2ee.requiredTx</arg></initMethod>
</component>
1つ目と2つ目は、S2AOPで用意されているInterceptorでトレース処理を行うものです。
詳しくはS2AOPで用意されているInterceptorを参照してください。
3つめのj2ee.requiredTxはトランザクション制御用コンポーネントです。トランザクションが開始されていなければ自動的にトランザクションを開始します。
既にトランザクションが開始されていれば、そのトランザクションを引き継ぎます。
詳しくはトランザクションの自動制御を参照してください。
Dao自動登録
app.diconでDaoをS2Containerへ登録している箇所はこのようになっています。
01:<component
02: class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister"
03:>
04: <initMethod name="addClassPattern">
05: <arg>"examples.jsf.dao"</arg>
06: <arg>".*Dao"</arg>
07: </initMethod>
08:</component>
09:
11:<component
12: class="org.seasar.framework.container.autoregister.AspectAutoRegister"
13:>
14: <property name="interceptor">daoInterceptorChain</property>
15: <initMethod name="addClassPattern">
16: <arg>"examples.jsf.dao"</arg>
17: <arg>".*Dao"</arg>
18: </initMethod>
19:</component>
01~08行目で、名称が"Dao"で終わるクラス(インタフェース)をrequestスコープへ登録しています。
10~18行目で、同じく"Dao"で名称が終わるクラス(インタフェース)にdaoInterceptorChainアスペクトを登録しています。
daoInterceptorChainはexamples/jsf/dicon/allaop.diconで設定されています。
<component
name="daoInterceptorChain"
class="org.seasar.framework.aop.interceptors.InterceptorChain"
>
<initMethod name="add"><arg>aop.traceInterceptor</arg></initMethod>
<initMethod name="add"><arg>dao.interceptor</arg></initMethod>
</component>
aspectタグのうちdao.interceptorは、dao.diconで定義されています。
これはS2Daoを機能させるためのInterceptor(org.seasar.dao.interceptors.S2DaoInterceptor)であり、
org.seasar.dao.impl.DaoMetaDataFactoryImplはS2DaoInterceptorのコンストラクタの引数となるのでとともに定義されています。
<components namespace="dao">
<include path="j2ee.dicon"/>
<component
class="org.seasar.dao.impl.DaoMetaDataFactoryImpl"/>
<component name="interceptor"
class="org.seasar.dao.interceptors.S2DaoInterceptor"/>
</components>
これらの定義によりEmployeeDtoDao・DepartmentDtoDaoはインタフェースに対して、
実装クラスを自動的に生成して、EmployeeLogicImplのプロパティに設定しています。
ブラウザで、http://localhost:8080/s2jsf-example/hello/hello.html?message=aaaにアクセスしてみましょう。
Hello aaaと表示されるはずです。このサンプルでは、次のことを学びます。
- S2JSF用の名前空間の宣言。
- レイアウト機能。
- taglibの利用方法。
- inject属性によるタグの指定。
- titleタグの使い方。
- リンクでページ遷移する方法。
- ページ遷移のときにパラメータを渡す方法。
- navigation-ruleの記述方法。
- ValueBindingを使って文字列を出力する方法。
それでは、HTMLの中身を見てみましょう。
01:<html xmlns:m="http://www.seasar.org/maya" m:extends="/WEB-INF/layout/layout.html">
02:<head>
03:<meta http-equiv="Content-Type" content="text/html; charset=Windows-31j" />
04:<title>Hello</title>
05:</head>
06:<body>
07: <span m:inject="f:param" m:name="layoutTitle" m:value="Hello"/>
08: <span m:inject="s:insert" m:name="body">
09: Hello <span m:value="#{message}">hoge</span>
10: </span>
11:</body>
12:</html>
1行目のxmlns:m="http://www.seasar.org/maya"で名前空間を指定しています。この
URI(http://www.seasar.org/maya)は固定です。プレフィックスのmは任意の値を指定することが出来ますが、慣例的にmを
使うことになっています。
次のextends属性で、継承するレイアウトを指定しています。
一貫したLook & Feelを提供するために、ページのレイアウトが、ヘッダー・メニュー・ボディ・フッターから構成され、
個別のページはボディ部分だけが異なるというのは、よく見かけるパターンではないでしょうか。
このようなニーズにこたえるために、S2JSFでは、ベースになるページでレイアウトを定義し、
個別のページではベースとなるページを継承して特定の部分(例えばボディ)だけを上書きするということが可能になっています。
それではlayout.htmlを見てみましょう。
01:<html xmlns:m="http://www.seasar.org/maya">
02:<head>
03:<meta http-equiv="Content-Type" content="text/html; charset=Windows-31j" />
04:<link m:inject="s:link" rel="stylesheet" type="text/css" href="/css/global.css"/>
05:<title m:value="#{layoutTitle}"/>
06:</head>
07:<body>
08:<table border="0" cellspacing="5">
09:<tr>
10: <td colspan="2"><span m:inject="s:insert" m:src="/WEB-INF/layout/header.html"/></td>
11:</tr>
12:<tr>
13: <td width="140" valign="top">
14: <span m:inject="s:insert" m:src="/WEB-INF/layout/menu.html"/>
15: </td>
16: <td valign="top" align="left">
17: <span m:inject="s:insert" m:name="body"/>
18: </td>
19:</tr>
20:<tr>
21: <td colspan="2">
22: <hr/>
23: </td>
24:</tr>
25:<tr>
26: <td colspan="2">
27: <span m:inject="s:insert" m:src="/WEB-INF/layout/footer.html"/>
28: </td>
29:</tr>
30:</table>
31:</body>
32:</html>
4行目のlinkタグを見てください。inject属性でs:linkというタグ名を指定しています。このタグ名の記述の仕方は、JSP
の場合と一緒です。sというプレフィックスは、WEB-INF/classes(WEB-INF/src)のjsf.diconに記述されています。jsf.dicon
の該当部分は次のようになります。
<component class="org.seasar.jsf.runtime.JsfConfigImpl">
<initMethod name="addTaglibUri">
<arg>"h"</arg>
<arg>"http://java.sun.com/jsf/html"</arg>
<initMethod>
<initMethod name="addTaglibUri">
<arg>"f"</arg>
<arg>"http://java.sun.com/jsf/core"</arg>
<initMethod>
<initMethod name="addTaglibUri">
<arg>"s"</arg>
<arg>"http://www.seasar.org/jsf"</arg>
</initMethod>
</component>
inject属性を指定することで、任意のHTMLのタグを任意のJSF用のJSPのタグに変換できます。既存のJSF用のtaglibがそ
のまま再利用できるのです。
5行目でtitleを指定しています。layoutTitleの実際の値は、このレイアウトを継承したページで指定します。
10行目のinsertタグのsrc属性でヘッダー用のページを挿入しています。ヘッダー用のページは次のようになっています。
<html xmlns:m="http://www.seasar.org/maya">
<body>
<span m:inject="s:insert">
<img m:inject="h:graphicImage" m:url="/images/seasar.gif"/>
</span>
</body>
</html>
挿入されるページでは、name属性のないinsertタグの中に挿入したい内容を記述します。insertタグの外側にプレビュ
ー用にタグを書くことも出来ます。その場合、実行時には、insertタグの外側は無視されます。次は、menu.htmlを見てみましょう。
<html xmlns:m="http://www.seasar.org/maya">
...
<body>
<span m:inject="s:insert">
<form>
<ul>
<li>
<a m:action="hello">Hello
<span m:inject="f:param" m:name="message" m:value="World"/>
</a>
</li>
<li>
<a m:action="add">Add</a>
</li>
...
</ul>
</form>
</span>
</body>
</html>
anchorタグのaction属性でリンクをクリックしたときに、遷移するページ名を指定します。
action属性は、${変数名.メソッド名}という形式でJavaBeansのメソッドを呼び出すことも出来ますが、単に文字列を指定した場合は指定したページに遷移するという意味になります。
このhelloというページ名はWEB-INF/faces-config.xmlで設定されています。
それでは、該当個所を見ていましょう。
<navigation-rule>
<navigation-case>
<from-outcome>hello</from-outcome>
<to-view-id>/hello/hello.html</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
from-outcomeでページ名(遷移先名)を指定します。to-view-idで指定するのが、実際のパスです。redirectタグを書くとリダイレクト、書かないとフォワードされることになります。menu.htmlの説明に戻ります。
anchorタグの子タグでf:paramタグを指定することにより、遷移先のページへパラメータを渡すことが出来ます。layout.htmlの説明に戻ります。
17行目の<span m:inject="s:insert" m:name="body"/>でボディ部分を定義しています。こ
のようにname属性で名前を付けておくと、このレイアウトを継承したページで同じように<span
m:inject="s:insert" m:name="body">...,</span>を定義することで、親のページの内容
を上書きすることが出来ます。それでは、hello.htmlの説明に戻ります。
7行目の<span m:inject="f:param" m:name="layoutTitle" m:value="Hello"/>
でlayout.htmlで定義したlayoutTitleの値を設定しています。親のページにパラメータを渡すには、このようにbodyタグの
子タグとしてf:paramタグを記述します。
8行目から10行目の<span m:inject="s:insert" m:name="body">で、親ページで定義した
内容を上書きします。
9行目の<span m:value="#{message}">hoge</span>でHTMLに文字列を出力します。#{...}の記述
は、ValueBindingと呼ばれ、動的に値を変える場合に使います。#{変数名}のように指定した場合は、リクエストの属性、
パラメータ、セッションの属性、S2Containerの順番で検索し、最初に見つかった値を設定します。#{変数名.プロパティ名
}のように指定した場合は、先程と同様に変数名を解決し、その変数のプロパティの値を設定します。この例では、リクエ
ストのmessageパラメータの内容が出力されることになります。ボディのhogeは実行時には無視されます。ブラウザで直接プ
レビューするときにレイアウトを確認しやすくするためのダミーデータになります。HTMLに出力される文字列を動的に変え
たい場合は、このようにspanタグのvalue属性で指定します。
このサンプルでは、次のことを学びます。
- 入力値をJavaBeansのプロパティと連動させる方法。
- ボタンをクリックしたときにJavaBeansのメソッドを呼び出す方法。
- Actionにsetterメソッドを定義してリクエスト、セッション、S2Containerのオブジェクトをプロパティに自動設定する方法。
それでは、HTMLの中身を見てみましょう。
01:<html xmlns:m="http://www.seasar.org/maya" m:extends="/WEB-INF/layout/layout.html">
02:<head>
03:<meta http-equiv="Content-Type" content="text/html; charset=Windows-31j" />
04:<title>Add</title>
05:</head>
06:<body>
07: <span m:inject="f:param" m:name="layoutTitle" m:value="Add"/>
08: <span m:inject="s:insert" m:name="body">
09: <form>
10: <span m:inject="h:messages" m:globalOnly="false" m:showDetail="true"/>
11: <input type="text" m:value="#{addDto.arg1}"/> +
12: <input type="text" m:value="#{addDto.arg2}"/> =
13: <span m:value="#{addDto.result}"/>
14: <input type="submit" value="calculate" m:action="#{addAction.calculate}"/>
15: </form>
16: </span>
17:</body>
18:</html>
headタグの部分は、実行時には無視されます。ブラウザでのプレビュー用です。
10行目の
<span m:inject="h:messages" m:globalOnly="false" m:showDetail="true"/>
で、エラーが起きたときのメッセージを出力する準備をしています。
globalOnly属性がtrueの場合、個別の入力項目には関係のない(globalな)エラーのメッセージだけが出力されます。
falseにするとglobalなメッセージも個別のメッセージも出力するようになります。
11行目の
<input type="text" m:value="#{addDto.arg1}"/>
で、入出力用のタグとJavaBeansのプロパティを連動させています。
入力した値は、バリデーションがOKならJavaBeansのプロパティに格納され、
NGの場合は入力された値がそのままページに表示されることになります。
このように入出力値の管理をきちんと行ってくれるところがJSFの偉いところです。
StrutsのようにActionFormでとりあえずStringで受け取るなんて事をする必要がないのです。
addDtoは、app.diconで解説したようにexamples.jsf.dto.AddDtoクラスです。
14行目の
<input type="submit" value="calculate" m:action="#{addAction.calculate}"/>
で、ボタンがクリックされたときにJavaBeansのメソッドを呼び出す設定をしています。
ボタンやリンクのaction属性で、#{変数名.メソッド名}のように記述することをMethodBindingと呼びます。
MethodBindingで呼び出されるメソッドは、引数がなく戻り値がStringでなければいけません。
addActionは、app.diconで解説したようにexamples.teeda.action.impl.AddActionImplクラスです。
それでは、AddActionインターフェース・AddActionImplクラスを見てみましょう。
インターフェースと実装を必ず分離しなければならないということはありませんが、
このように分離すると仕様が明確に伝わり易いと思います。
package examples.jsf.action;
public interface AddAction {
public String calculate();
}
package examples.jsf.action.impl;
import examples.jsf.action.AddAction;
import examples.jsf.dto.AddDto;
import examples.jsf.logic.AddLogic;
public class AddActionImpl implements AddAction {
private AddDto addDto;
private AddLogic addLogic;
public void setAddDto(AddDto addDto) {
this.addDto = addDto;
}
public void setAddLogic(AddLogic addLogic) {
this.addLogic = addLogic;
}
public String calculate() {
int result = addLogic.calculate(addDto);
addDto.setResult(result);
return null;
}
}
入力された値を受け取るために、addDtoプロパティのsetterメソッドを定義しています。
S2JSFはMethodBindingで指定したメソッドを呼び出す直前にsetterメソッドの定義されているプロパティがないか調べ、
もしある場合はプロパティ名と同様の変数がリクエスト属性・リクエストパラメータ・セッション属性・S2Containerに存在するか調べ、
もし存在する場合には自動的にsetterメソッドを呼び出してプロパティ名と同様の変数を設定します。
プロパティの型がインターフェースの場合はS2Containerによってそのインターフェースを実装したクラスが自動的に設定されます。
AddLogicインタフェースを実装したActionはexamples.jsf.logic.impl.AddLogicImplです。
このクラスはapp.diconの自動登録設定によってS2Containerへ登録されます。
MethodBindingで呼び出したメソッド(calculate)の戻り値がnullの場合は、自分自身に遷移することになります。
この場合はadd.htmlへ遷移します。
このサンプルでは、次のことを学びます。
- forEachタグによる繰り返し。
- anchorタグでプレビュー時にページ遷移させる方法。
- buttonrタグでプレビュー時にページ遷移させる方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <table border="1">
03: <tr bgcolor="#7777FF">
04: <th>Key</th>
05: <th>Name</th>
06: <th colspan="2">to ResultPage</th>
07: </tr>
08: <span m:inject="s:forEach" m:items="#{forEachDtoList}"
09: m:var="e" m:varIndex="i">
10: <tr>
11: <td><span m:value="#{e.key}">111</span></td>
12: <td><span m:value="#{e.name}">aaa</span></td>
13: <td><a href="forEachResult.html" m:action="forEachResult">to ResultPage
14: <span m:inject="f:param" m:name="index" m:value="#{i}"/>
15: </a>
16: </td>
17: <td>
18: <input type="button" m:action="forEachResult" value="to ResultPage"
19: onclick="location.href='forEachResult.html'">
20: <span m:inject="f:param" m:name="index" m:value="#{i}"/>
21: </input>
22: </td>
23: </tr>
24: </span>
25: </table>
26:</form>
8行目のforEachタグで繰り返しを指定します。items属性で繰り返す対象を、var属性で繰り返す際に個々の要素にアクセスするときの名前を、varIndex属性で繰り返す際にインデックスにアクセスするときの名前を指定します。
11行目のようにvar属性でつけた名前.プロパティという形でアクセスすることが出来ます。
13行目のanchorタグでhref属性とaction属性を両方指定しています。このように両方指定した場合、hrefは実行時には無視されます。ブラウザで直接プレビューしたときに、ページを遷移させるために記述しているのです。子タグのparamタグでページ遷移のときのパラメータを指定することができます。
18行目のbuttonタグもaction属性とonclick属性を指定しています。onclick属性のlocation.href='forEachResult.html'の部分は実行時には無視されます。ブラウザで直接プレビューしたときに、ページを遷移させるために記述しているのです。子タグのparamタグでページ遷移のときのパラメータを指定することができます。
このサンプルでは、次のことを学びます。
- ページの初期処理。
- Actionにgetterメソッドを定義してプロパティをリクエストやセッションに自動設定する方法。
それでは、HTMLを見てみましょう。
01:<html xmlns:m="http://www.seasar.org/maya"
02: m:action="#{forEachResultInitAction.initialize}"
03: m:extends="/WEB-INF/layout/layout.html">
04:<head>
05:<meta http-equiv="Content-Type" content="text/html; charset=Windows-31j" />
06:<title>ForEach</title>
07:</head>
08:<body>
09:<span m:inject="f:param" m:name="layoutTitle" m:value="ForEach"/>
10:<span m:inject="s:insert" m:name="body">
11: Key:<span m:value="#{forEachDto.key}">111</span>
12: Name:<span m:value="#{forEachDto.name}">aaa</span><br />
13: <a href="forEachList.html">previous</a>
14:</span>
15:</body>
16:</html>
2行目のhtmlタグのaction属性で、ページを初期処理するメソッドを指定しています。
それでは、Actionクラスを見てみましょう。
package examples.jsf.action.impl;
import java.util.List;
import examples.jsf.action.ForEachResultInitAction;
import examples.jsf.dto.ForEachDto;
public class ForEachResultInitActionImpl implements ForEachResultInitAction {
private int index;
private List forEachDtoList;
private ForEachDto forEachDto;
public void setIndex(int index) {
this.index = index;
}
public void setForEachDtoList(List forEachDtoList) {
this.forEachDtoList = forEachDtoList;
}
public ForEachDto getForEachDto() {
return forEachDto;
}
public String initialize() {
forEachDto = (ForEachDto) forEachDtoList.get(index);
return null;
}
}
前のページで、anchorタグやbuttonタグのパラメータでindexを渡していたことを思い出してください。
setIndex()メソッドを定義しておけば、S2JSFが自動的にパラメータの値を設定してくれます。
また、setForEachDtoList()メソッドを定義しておくことで、examples/jsf/dicon/foreach.diconに定義されているforEachDtoListを参照できます。
initMethodタグを使えば、任意のメソッドを呼び出せるので、自由自在にオブジェクトを組み立てることが出来ます。
<component name="forEachDtoList" class="java.util.ArrayList" instance="session">
<initMethod name="add" >
<arg>
<component class="examples.jsf.dto.ForEachDto" instance="prototype">
<property name="key">"111"</property>
<property name="name">"aaa"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.dto.ForEachDto" instance="prototype">
<property name="key">"222"</property>
<property name="name">"bbb"</property>
</component>
</arg>
</initMethod>
</component>
initialize()メソッドで指定されたインデックスに基づくオブジェクトをforEachDtoプロパティに設定しています。
forEachDtoプロパティにはgetterメソッドが定義されているので、
initialize()メソッドが実行された後にS2JSFによってforEachDtoプロパティの値が自動的にリクエスト属性(forEachDto)に設定されます。
public static final String プロパティ名_EXPORT = "session";
のように定数を宣言することで、リクエストではなくセッションの属性に設定することも出来ます。
このサンプルでは、次のことを学びます。
- 入力系のタグを含んだforEachタグによる繰り返し。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <table border="1">
03: <tr bgcolor="#7777FF">
04: <th>delete</th>
05: <th>input</th>
06: </tr>
07: <span m:inject="s:forEach" m:items="#{forEach2DtoList}" m:var="e">
08: <tr>
09: <td><input type="checkbox" m:value="#{e.delete}"/></td>
10: <td><input type="text" m:value="#{e.input}"</td>
11: </tr>
12: </span>
13: </table>
14: <input type="submit" value="update" m:action="#{forEach2ListAction.update}"/>
15: <input type="submit" value="add row" m:action="#{forEach2ListAction.addRow}"/>
16:</form>
このページでは、add rowボタンをクリックすると新規の行が挿入され、deleteのチェックボックスをチェックしてupdateボタンをクリックすることで行を削除することができます。また、入力値を書き換えてupdateボタンをクリックすることでinputの内容を更新することも出来ます。
入力系のタグを含んでいるといっても、forEachタグの使い方に特に違いはありません。Actionクラスは次のようになります。
package examples.jsf.action.impl;
import java.util.Iterator;
import java.util.List;
import examples.jsf.action.ForEach2ListAction;
import examples.jsf.dto.ForEach2Dto;
public class ForEach2ListActionImpl implements ForEach2ListAction {
private List forEach2DtoList;
public void setForEach2DtoList(List forEach2DtoList) {
this.forEach2DtoList = forEach2DtoList;
}
public String update() {
for (Iterator i = forEach2DtoList.iterator(); i.hasNext(); ) {
ForEach2Dto dto = (ForEach2Dto) i.next();
if (dto.isDelete()) {
i.remove();
}
}
return null;
}
public String addRow() {
forEach2DtoList.add(new ForEach2Dto());
return null;
}
}
このサンプルでは、次のことを学びます。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <table border="1">
03: <span m:inject="s:forEach" m:items="#{forEach3DtoList}" m:var="e">
04: <tr>
05: <span m:inject="s:forEach" m:items="#{e}" m:var="e2">
06: <td><input type="text" m:value="#{e2.value}"</td>
07: </span>
08: </tr>
09: </span>
10: </table>
11: <input type="submit" value="submit"/>
12:</form>
5行目を見てみるとわかるように、forEachタグの中でforEachタグを使用して繰り返しを行っています。forEach3DtoListはexamples/jsf/dicon/foreach.diconを見るとListの中にListがありますので、初めのforEachタグでvar属性をeとしているので2つ目のforEachタグでitems属性を"#{e}"とすることでforEach3DtoListの中のListを使用して繰り返し処理を行います。
<components>
<component name="forEach3DtoList" class="java.util.ArrayList" instance="session">
<initMethod name="add" >
<arg>
<component class="java.util.ArrayList" instance="prototype">
<initMethod name="add">
<arg>
new examples.jsf.dto.ForEach3Dto("11")
</arg>
</initMethod>
<initMethod name="add">
<arg>
new examples.jsf.dto.ForEach3Dto("12")
</arg>
</initMethod>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="java.util.ArrayList" instance="prototype">
<initMethod name="add">
<arg>
new examples.jsf.dto.ForEach3Dto("21")
</arg>
</initMethod>
<initMethod name="add">
<arg>
new examples.jsf.dto.ForEach3Dto("22")
</arg>
</initMethod>
</component>
</arg>
</initMethod>
</component>
</components>
このサンプルでは、次のことを学びます。
- converter属性の使い方。
- エラーメッセージにm:labelを使用する方法。
それでは、HTMLの一部を見てみましょう。
<form>
<span m:inject="h:messages" m:globalOnly="false" m:showDetail="true"/>
<input type="text" m:value="#{converterDto.aaa}" m:label="Hire Date"
m:converter="#{inputDateTimeConverter}"/><br />
<span m:value="#{converterDto.aaa}"
m:converter="#{outputDateTimeConverter}"/><br />
<input type="submit" value="submit"/>
</form>
ほとんどの入出力用のタグはconverter属性を持っていて、
Javaのオブジェクトの値を文字列に変換したり、入力された文字列を適切なオブジェクトの型に変換するためのコンバータを指定することができます。
この例におけるコンバータは、examples/jsf/dicon/allconverter.diconで次のように定義されています。
<component name="inputDateTimeConverter" class="org.seasar.jsf.convert.S2DateTimeConverter">
<property name="pattern">"yyyyMMdd"</property>
</component>
<component name="outputDateTimeConverter" class="org.seasar.jsf.convert.S2DateTimeConverter">
<property name="pattern">"yyyy/MM/dd"</property>
</component>
このようにコンバータをHTMLで個別に定義するのではなくdiconファイルで一元管理することで、
コンバータの定義が分散することを防ぐことが出来ます。
また、label属性に定義した値をコンバート失敗時のメッセージに使用することが出来ます。
それにはConverterがlabel属性に対応している必要があります。
標準のConverterについては、それぞれ対応するConverterをS2JSFで用意しています。
独自のConverterを作る場合はS2JSFを参考にしてください。
このサンプルでは、次のことを学びます。
- validatorタグの使い方。
- 関連チェックを行うバリデーションの方法。
- messageタグの使い方。
- バリデーションのメッセージでm:labelを使用する方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <span m:inject="h:messages" m:globalOnly="true"/>
03: UserName(2 letters or more):
04: <input id="userName" type="text"
05: m:label = "User Name"
06: m:value="#{validatorDto.userName}" m:required="true">
07: <span m:inject="s:validator" m:binding="#{userNameLengthValidator}"/>
08: </input>
09: <span m:inject="h:message" m:for="userName"/><br />
10: From:
11: <input id="from" type="text"
12: m:label = "From">
13: </input> -
14: To:
15: <input id="to" type="text"
16: m:label = "To">
17: <span m:inject="s:validator"
18: m:binding="#{greaterEqualValidator}"
19: m:targetId="from"/>
20: </input>
21: <span m:inject="h:message" m:for="to"/><br />
22: <input type="submit" value="submit"/>
23:</form>
バリデーションをかけたい場合、4行目のようにバリデーションの対象となるタグ(id:userName)の子タグとしてvalidatorタグを記述します。関連チェックバリデーションをかけたい場合、15行目のようにバリデーションの対象となるタグ(id:to)の子タグとしてvalidatorタグを記述します。validatorタグでは、targetId属性で関連チェックする相手を指定します。実際のバリデータは、validatorタグのbinding属性で指定します。この例におけるバリデータは、examples/jsf/dicon/allvalidator.diconで次のように定義されています。
<component name="userNameLengthValidator" class="javax.faces.validator.LengthValidator">
<property name="minimum">2</property>
</component>
<component name="greaterEqualValidator" class="org.seasar.jsf.validator.S2GreaterEqualValidator"
instance="prototype"
/>
S2GreaterEqualValidatorは、targetIdで関連チェックする相手を指定するのですが、
diconファイルの定義は1つでhtmlでtargetIdを追加するようになっています。
同じような処理で複数指定する場合があるので、diconファイルのinstance属性はprototypeにします。
このようにバリデータをHTMLで個別に定義するのではなくdiconファイルで一元管理することで、バリデータの定義が分散することを防ぐことが出来ます。
バリデーションエラーになったときに表示させるメッセージはmessageタグで組み込みます。
for属性で対象となるタグのidを指定します。
messagesタグでglobalOnly属性をfalseにすることで、messagesタグに表示させることも出来ますが、
どのタグがバリデーションエラーになったのかわかりやすくするために、
個別のタグのバリデーションエラーはmessageタグを使って表示させたほうが良いでしょう。
また、label属性に定義した値をバリデーションのメッセージに使用することが出来ます。
lbel属性を使用する場合は、UIComponentとValidatorがそれぞれlabel属性に対応している必要があります。
JSF標準のUIComponentとValidatorについては、それぞれ対応するUIComponentとValidatorをS2JSFで用意しています。
独自のUIComponent・Validatorを作る場合は、S2JSFを参考にして機能を追加してください。
このサンプルでは、次のことを学びます。
それでは、HTMLの一部を見てみましょう。
<form>
<input type="checkbox" m:value="#{checkboxDto.aaa}"/>hoge<br />
<span m:value="#{checkboxDto.aaa}"/><br />
<input type="submit" value="submit"/>
</form>
特に難しいところはありませんね。うれしいのは、チェックボックスをオフにしたときも、それがサーバサイドにきちんと伝わることです。
このサンプルでは、次のことを学びます。
- selectタグで直接optionで要素を定義する方法。
- selectタグでSelectItemで要素を定義する方法。
- selectタグでJavaBeansで要素を定義する方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <select m:value="#{selectOneMenuDto.aaa}">
03: <option value="1">One</option>
04: <option value="2">Two</option>
05: <option value="3">Three</option>
06: </select><br />
07: <span m:value="#{selectOneMenuDto.aaa}"/><br />
08:
09: <select m:value="#{selectOneMenuDto.bbb}"
10: m:items="#{selectOneMenuBbbItems}">
11: <option value="">Please select</option>
12: <option value="1">One</option>
13: <option value="2">Two</option>
14: </select><br />
15: <span m:value="#{selectOneMenuDto.bbb}"/><br />
16:
17: <select m:value="#{selectOneMenuDto.ccc}"
18: m:items="#{selectOneMenuCccItems}"
19: m:itemValue="deptno"
20: m:itemLabel="dname"
21: m:nullLabel="Please select">
22: <option value="">Please select</option>
23: <option value="10">ACOUNTING</option>
24: </select><br />
25: <span m:value="#{selectOneMenuDto.ccc}"/><br />
26:
27: <input type="submit" value="submit"/>
28:</form>
2行目からはじまるselectタグは、子タグのoptionタグで直接要素を指定しています。
9行目からはじまるselectタグは、items属性で要素を指定しています。items属性を指定した場合、子タグのoptionタグは実行時には無視されます。あくまでもブラウザでのプレビュー用です。selectOneMenuBbbItemsは、examples/jsf/dicon/selectonemenu.diconで次のように定義されています。
<component name="selectOneMenuBbbItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="label">"Please select"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">1</property>
<property name="label">"One"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">2</property>
<property name="label">"Two"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">3</property>
<property name="label">"Three"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">4</property>
<property name="label">"Four"</property>
</component>
</arg>
</initMethod>
</component>
この例では、SelectItemを使って要素を定義しています。
17行目からはじまるselectタグは、items属性で要素を指定しています。この例の場合、個々の要素はJavaBeansなので、さらにitemValue属性とitemLabel属性で、どのプロパティを値として使うのか、どのプロパティをラベルとして使うのかを指定します。nullLabel属性を使いnullを設定する場合のラベルを指定することもできます。selectOneMenuCccItemsは、examples/jsf/dicon/selectonemenu.diconで次のように定義されています。
<component name="selectOneMenuCccItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">10</property>
<property name="dname">"ACOUNTING"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">20</property>
<property name="dname">"RESEARCH"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">30</property>
<property name="dname">"SALES"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">40</property>
<property name="dname">"OPERATIONS"</property>
</component>
</arg>
</initMethod>
</component>
このサンプルでは、次のことを学びます。
- selectManyCheckboxで直接checkboxで要素を定義する方法。
- selectManyCheckboxでSelectItemで要素を定義する方法。
- selectManyCheckboxでJavaBeansで要素を定義する方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <span m:inject="s:selectManyCheckbox"
03: m:value="#{selectManyCheckboxDto.aaa}">
04: <input type="checkbox" m:inject="s:selectItem"
05: m:itemLabel="One" value="1"/>One
06: <input type="checkbox" m:inject="s:selectItem"
07: m:itemLabel="Two" value="2" checked="checked"/>Two
08: <input type="checkbox" m:inject="s:selectItem"
09: m:itemLabel="Three" value="3"/>Three
10: </span><br />
11:
12: <span m:inject="s:selectManyCheckbox"
13: m:value="#{selectManyCheckboxDto.bbb}"
14: m:items="#{selectManyCheckboxBbbItems}">
15: <input type="checkbox" value="1"/>One
16: <input type="checkbox" value="2"/>Two
17: <input type="checkbox" value="3"/>Three
18: </span><br />
19:
20: <span m:inject="s:selectManyCheckbox"
21: m:value="#{selectManyCheckboxDto.ccc}"
22: m:items="#{selectManyCheckboxCccItems}"
23: m:itemValue="deptno"
24: m:itemLabel="dname">
25: <input type="checkbox" value="10"/>ACCOUNTING
26: </span><br />
27:
28: <input type="submit" value="submit"/>
29:</form>
2行目からはじまるselectManyCheckboxタグは、子タグのcheckboxタグで直接要素を指定しています。チェックされた値は、配列としてvalue属性で指定したプロパティに設定されます。
12行目からはじまるselectManyCheckboxタグは、items属性で要素を指定しています。items属性を指定した場合、子タグのcheckboxタグは実行時には無視されます。あくまでもブラウザでのプレビュー用です。selectManyCheckboxBbbItemsは、examples/jsf/dicon/selectmanycheckbox.diconで次のように定義されています。
<component name="selectManyCheckboxBbbItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">1</property>
<property name="label">"One"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">2</property>
<property name="label">"Two"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">3</property>
<property name="label">"Three"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">4</property>
<property name="label">"Four"</property>
</component>
</arg>
</initMethod>
</component>
この例では、SelectItemを使って要素を定義しています。
20行目からはじまるselectManyCheckboxタグは、items属性で要素を指定しています。この例の場合、個々の要素はJavaBeansなので、さらにitemValue属性とitemLabel属性で、どのプロパティを値として使うのか、どのプロパティをラベルとして使うのかを指定します。selectManyCheckboxCccItemsは、examples/jsf/dicon/selectmanycheckbox.diconで次のように定義されています。
<component name="selectManyCheckboxCccItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">10</property>
<property name="dname">"ACOUNTING"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">20</property>
<property name="dname">"RESEARCH"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">30</property>
<property name="dname">"SALES"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">40</property>
<property name="dname">"OPERATIONS"</property>
</component>
</arg>
</initMethod>
</component>
このサンプルでは、次のことを学びます。
- selectManyListboxで直接checkboxで要素を定義する方法。
- selectManyListboxでSelectItemで要素を定義する方法。
- selectManyListboxでJavaBeansで要素を定義する方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <select m:value="#{selectManyListboxDto.aaa}" multiple="multiple">
03: <option value="1">One</option>
04: <option value="2">Two</option>
05: <option value="3">Three</option>
06: </select><br />
07:
08: <select m:value="#{selectManyListboxDto.bbb}"
09: m:items="#{selectManyListboxBbbItems}"
10: multiple="multiple">
11: <option value="1">One</option>
12: <option value="2">Two</option>
13: </select><br />
14:
15: <select m:value="#{selectManyListboxDto.ccc}"
16: m:items="#{selectManyListboxCccItems}"
17: m:itemValue="deptno"
18: m:itemLabel="dname"
19: multiple="multiple">
20: <option value="10">ACOUNTING</option>
21: </select><br />
22:
23: <input type="submit" value="submit"/>
24:</form>
2行目からはじまるselectタグは、子タグのoptionタグで直接要素を指定しています。選択された値は、配列としてvalue属性で指定したプロパティに設定されます。
8行目からはじまるselectタグは、items属性で要素を指定しています。items属性を指定した場合、子タグのoptionタグは実行時には無視されます。あくまでもブラウザでのプレビュー用です。selectManyListboxBbbItemsは、examples/jsf/dicon/selectmanylistbox.diconで次のように定義されています。
<component name="selectManyListboxBbbItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">1</property>
<property name="label">"One"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">2</property>
<property name="label">"Two"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">3</property>
<property name="label">"Three"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="javax.faces.model.SelectItem">
<property name="value">4</property>
<property name="label">"Four"</property>
</component>
</arg>
</initMethod>
</component>
この例では、SelectItemを使って要素を定義しています。
15行目からはじまるselectタグは、items属性で要素を指定しています。この例の場合、個々の要素はJavaBeansなので、さらにitemValue属性とitemLabel属性で、どのプロパティを値として使うのか、どのプロパティをラベルとして使うのかを指定します。selectManyListboxCccItemsは、examples/jsf/dicon/selectmanylistbox.diconで次のように定義されています。
<component name="selectManyListboxCccItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">10</property>
<property name="dname">"ACOUNTING"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">20</property>
<property name="dname">"RESEARCH"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">30</property>
<property name="dname">"SALES"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">40</property>
<property name="dname">"OPERATIONS"</property>
</component>
</arg>
</initMethod>
</component>
このサンプルでは、次のことを学びます。
- selectOneRadioで直接radioで要素を定義する方法。
- selectOneRadioで必須チェックを定義する方法。
- selectOneRadioでJavaBeansで要素を定義する方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <span m:inject="s:selectOneRadio2" id="radio1"
03: m:value="#{selectOneRadioDto.aaa}">
04: <input type="radio"
05: name="aaa"/>Non-selection
06: <input type="radio"
07: name="aaa" value="1"/>One
08: <input type="radio"
09: name="aaa" value="2" checked="checked"/>Two
10: <input type="radio"
11: name="aaa" value="3"/>Three
12: </span><br />
13:
14: <span m:inject="s:selectOneRadio2" id="radio2"
15: m:value="#{selectOneRadioDto.bbb}"
16: m:required="true">
17: <input type="radio"
18: name="bbb" value="1"/>One
19: <input type="radio"
20: name="bbb" value="2"/>Two
21: <input type="radio"
22: name="bbb" value="3"/>Three
23: </span><br />
24:
25: <span m:inject="s:selectOneRadio2"
26: m:value="#{selectOneRadioDto.ccc}">
27: <span m:inject="s:forEach"
28: m:items="#{selectOneRadioCccItems}"
29: m:var="e">
30: <input type="radio"
31: name="ccc" value="#{e.deptno}"/>
32: <span m:value="#{e.dname}">ACOUNTING</span>
33: </span>
34: <span m:rendered="false">
35: <input type="radio"
36: name="ccc" value="20"/>RESEARCH
37: </span>
38: </span><br />
39:
40: <input type="submit" value="submit"/>
41:</form>
2行目からはじまるselectOneRadio2タグは、子タグのradioタグで直接要素を指定しています。選択された値は、value属性で指定したプロパティに設定されます。4行目からはじまるradioタグのようにvalue属性を記述しない場合は、nullが設定されます。
14行目からはじまるselectOneRadio2タグは、required属性で"true"を指定しています。required属性で"true"を指定した場合、実行時に必須チェックが行われます。選択されていない場合、次のようなバリデーションエラーメッセージが表示されます。
- バリデーションエラー "radio2": 値を入力して下さい.
この例では、forEachを使って要素を定義しています。
25行目からはじまるselectOneRadio2タグは、子タグのforEachタグのitems属性で要素を指定しています。この例の場合、個々の要素はJavaBeansなので、さらにradioタグのvalue属性と32行目からはじまるタグのvalue属性で、どのプロパティを値として使うのか、どのプロパティをラベルとして使うのかを指定します。
34行目からはじまるタグは、rendered属性で"false"を指定しています。rendered属性で"false"を指定した場合、子タグのradioタグは実行時には無視されます。selectOneRadioCccItemsは、examples/jsf/dicon/selectoneradio.diconで次のように定義されています。
<component name="selectOneRadioCccItems" class="java.util.ArrayList">
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">10</property>
<property name="dname">"ACOUNTING"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">20</property>
<property name="dname">"RESEARCH"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">30</property>
<property name="dname">"SALES"</property>
</component>
</arg>
</initMethod>
<initMethod name="add" >
<arg>
<component class="examples.jsf.entity.Department">
<property name="deptno">40</property>
<property name="dname">"OPERATIONS"</property>
</component>
</arg>
</initMethod>
</component>
このサンプルでは、次のことを学びます。
- textareaの入力値をJavaBeansのプロパティと連動させる方法。
それでは、HTMLの一部を見てみましょう。
01:<form>
02: <span m:inject="h:messages" m:globalOnly="false" m:showDetail="true"/>
03: <textarea m:rows="5" m:cols="50"
04: wrap="soft" m:value="#{inputTextareaDto.aaa}">for test</textarea>
05: <input type="submit" value="submit"/>
06:</form>
3行目からはじまるtextareaタグのvalue属性で、JavaBeansのプロパティを連動させています。
入力した値は、バリデーションがOKならJavaBeansのプロパティに格納され、
NGの場合は入力された値がそのままページに表示されることになります。
textareaタグの要素は、あくまでもブラウザでのプレビュー用です。<
/p>
このサンプルでは、次のことを学びます。
- バリデーションのメッセージでm:labelを使用する方法。
それでは、HTMLの中身から見てみましょう。
<span m:inject="h:messages" m:globalOnly="false" m:showDetail="true"/>
<input type="password" m:required="true" m:label="password"/>
<input type="submit" value="submit"/>
m:required="true"としてあるので、何も入力せずにボタンをクリックすると以下のようにエラーメッセージにlabal属性のpasswordが使用されているのが分かります。
バリデーションエラー "password": を値を入力して下さい
このサンプルでは、次のことを学びます。
それでは、HTMLの中身から見てみましょう。
01:<form>
02: <input type="button" value="The exception due to action"
03 m:action="#{page1Action.throwException}"/>
03: <input type="button" value="The exception due to initialize action" m:action="pageTran2"
04: onclick="location.href='pageTran2.html'" m:immediate="true"/>
05:</form>
1つ目のボタンタグのaction属性のActionクラスを見てみましょう。
public class Page1ActionImpl implements Page1Action {
public String throwException() {
throw new ErrorPage1RuntimeException("errorPage1");
}
}
examples.jsf.action.impl.Page1ActionImplクラスのthrowExceptionはErrorPage1RuntimeExceptionを発生させているだけです。
ErrorPage1RuntimeExceptionはjava.lang.RuntimeExceptionのサブクラスでメッセージに"errorPage1"をセットしています。
ErrorPage1RuntimeExceptionのエラーが発生した場合の画面の遷移先はjsfErrorPage.diconで以下のように設定しています。
<component class="org.seasar.jsf.runtime.ErrorPageManagerImpl">
<initMethod name="addErrorPage">
<arg>@examples.jsf.exception.ErrorPage1RuntimeException@class</arg>
<arg>"/errorpage/errorPage1.html"</arg>
</initMethod>
<initMethod name="addErrorPage">
<arg>@examples.jsf.exception.ErrorPage2RuntimeException@class</arg>
<arg>"/errorpage/errorPage2.html"</arg>
</initMethod>
<initMethod name="addErrorPage">
<arg>@org.seasar.framework.exception.SQLRuntimeException@class</arg>
<arg>"/errorpage/errorPageHsqldb.html"</arg>
</initMethod>
</component>
org.seasar.jsf.runtime.ErrorPageManagerImplクラスでエラーが発生した場合の画面の遷移先を管理します。今回のErrorPage1RuntimeExceptionの場合は、errorPage1.htmlに遷移します。このようにしてエラーページの設定をjsfErrorPage.diconにまとめて管理することができます。
errorPage1.htmlではm:value="#{requestScope['seasar.jsf.error.message']}"でErrorPage1RuntimeExceptionでセットしたメッセージを表示しています。
<body>
<span m:inject="f:param" m:name="layoutTitle" m:value="The page transition with exception1"/>
<span m:inject="s:insert" m:name="body">
<span m:value="#{requestScope['seasar.jsf.error.message']}">error message</span>
</span>
</body>
それでは、pageTran1.htmlのもう一つのボタンを見てみましょう。
<input type="button" value="The exception due to initialize action" m:action="pageTran2"
onclick="location.href='pageTran2.html'" m:immediate="true"/>
action属性のpageTran2はWEB-INF/faces-config.xmlでは/errorpage/pageTran2.htmlと定義されています。
pageTran2.htmlを見てみましょう
01:<html xmlns:m="http://www.seasar.org/maya"
02: m:action="#{page2InitAction.initialize}"
03: m:extends="/WEB-INF/layout/layout.html">
2行目のaction属性のActionクラスを見てみましょう。
public class Page2InitActionImpl implements Page2InitAction {
public String initialize() {
throw new ErrorPage2RuntimeException("errorPage2");
}
}
この処理も先ほどと同じようにErrorPage2RuntimeExceptionを発生させています。
ErrorPage2RuntimeExceptionはjsfErrorPage.diconを見るとerrorPage2.htmlに遷移するように設定してありますので、
errorPage2.htmlに遷移して設定したメッセージが表示されます。
このサンプルの従業員管理アプリケーションでは新規・修正・削除・照会を行います。
このサンプルを動かす前にs2jsf-example/WEB-INF/binにあるrunHsqldb.batでHSQLDBを起動させておいてください。
s2jsfでは、Actionクラス・Logicクラス・dicon・Dto・Daoという構成でなりたっています。
今回のサンプルは以下のようになっています。
・Actionクラス
examples/jsf/action
EmployeeConfirmAction.java (確認処理インターフェース)
EmployeeConfirmInitAction.java (確認初期処理Actionインターフェース)
EmployeeEditAction.java (編集処理インターフェース)
EmployeeEditInitAction.java (編集初期処理インターフェース)
EmployeeListAction.java (一覧処理インターフェース)
EmployeeListInitAction.java (一覧初期処理インタフェース)
EmployeeSearchAction.java (検索処理インターフェース)
EmployeeSearchInitAction.java (検索初期処理インターフェース)
・Action実装クラス
examples/jsf/action/impl
EmployeeConfirmActionImpl.java (確認処理実装クラス)
EmployeeConfirmInitActionImpl.java (確認初期処理実装クラス)
EmployeeEditActionImpl.java (編集処理実装クラス)
EmployeeEditInitActionImpl.java (編集初期処理実装クラス)
EmployeeListActionImpl.java (一覧処理実装クラス)
EmployeeListInitActionImpl.java (一覧初期処理実装クラス)
EmployeeSearchActionImpl.java (検索処理実装クラス)
EmployeeSearchInitActionImpl.java (検索初期処理実装クラス)
・ロジッククラス
examples/jsf/logic
EmployeeLogic.java (業務ロジックインタフェース)
・ロジック実装クラス
examples/jsf/logic/impl
EmployeeLogicImpl.java (業務ロジック実装クラス)
・diconファイル
examples/jsf/dicon
employee.dicon (各Actionクラスとロジッククラスの定義ファイル)
・Dto
examples/jsf/dto
EmployeeSearchDto.java (検索画面用Dto)
EmployeeDto.java (従業員用Dto)
DepartmentDto.java (部署用Dto)
・Dao
examples/jsf/Dao
DepartmentDtoDao.java (DEPTテーブルDao)
EmployeeDtoDao.java (EMPテーブルDao)
それでは、初めのemployeeSearch.htmlから見てみましょう。見ていく順番は以下のようになります。
01:<html xmlns:m="http://www.seasar.org/maya"
02: m:action="#{employeeSearchInitAction.initialize}"
03: m:extends="/WEB-INF/layout/layout.html">
2行目のaction属性で、ページを初期化するコンポーネント(employeeSearchInitAction)とそのメソッド(initialize)を指定しています。
この初期処理では、下記selectタグのitems属性のdepartmentDtoListにDAOから取得したJavaBeanのListをセットします。
71:<select id="deptno" m:value="#{employeeSearchDto.deptno}"
72: m:items="#{departmentDtoList}"
73: m:itemValue="deptno"
74: m:itemLabel="dname"
75: m:nullLabel="Please select">
76: <option value="">Please select</option>
77: <option value="10">ACCOUNTING</option>
78: <option value="20">RESEARCH</option>
79: <option value="30">SALES</option>
80: <option value="40">OPERATIONS</option>
81:</select>
employeeSearchInitActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeSearchInitActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
actionInterceptorChainの中のactionThrowsInterceptorによって、発生した例外のメッセージは下記のメッセージタグにセットされます。
<span m:inject="h:messages" m:globalOnly="true" m:class="error"/>
examples.jsf.action.impl.EmployeeSearchInitActionImplを見ていきましょう。
初期処理のActionクラスは次のプロパティを持っています。
public class EmployeeSearchInitActionImpl implements EmployeeSearchInitAction {
private EmployeeLogic employeeLogic;
private List departmentDtoList;
:
}
EmployeeLogicはインターフェースなので、S2ContainerによってEmployeeLogicを実装したクラスが自動的に設定されます。
EmployeeLogicを実装したクラスはexamples.jsf.logic.impl.EmployeeLogicImplです。
app.diconでの設定によりS2Containerへ登録されています。
Logic自動登録で解説したようにLogicにはlogicInterceptorChainが指定されており、
logicInterceptorChainの中のj2ee.requiredTxによって、トランザクションが自動制御されます。
EmployeeLogicImplは以下の2つのsetterメソッドに対してDAOを自動バインディングしてセットします。
DAOの詳しい説明はS2Daoを参照してください。
public class EmployeeLogicImpl implements EmployeeLogic {
private EmployeeDtoDao employeeDtoDao;
private DepartmentDtoDao departmentDtoDao;
public void setEmployeeDtoDao(EmployeeDtoDao employeeDtoDao) {
this.employeeDtoDao = employeeDtoDao;
}
public void setDepartmentDtoDao(DepartmentDtoDao departmentDtoDao) {
this.departmentDtoDao = departmentDtoDao;
}
:
:
EmployeeDtoDaoではBEANアノテーションでexamples.jsf.dto.EmployeeDtoを定義しています。
public interface EmployeeDtoDao {
public Class BEAN = EmployeeDto.class;
:
}
BEANアノテーションで指定しているexamples.jsf.dto.EmployeeDtoはexamples.jsf.entity.Employeeのサブクラスで、
このexamples.jsf.entity.EmployeeでTABLEアノテーションでEMPテーブルとの関連付けを行っています。
public class Employee implements Serializable {
public static final String TABLE = "EMP";
:
}
examples.jsf.dto.EmployeeDtoはプロパティdnameとそのsetter/getterを追加したもので、
これはDepartment.dname用のものです。
もう一方のDepartmentDtoDaoも同じようにBEANアノテーションでexamples.jsf.dto.DepartmentDtoを定義しています。
public interface DepartmentDtoDao {
public Class BEAN = DepartmentDto.class;
:
}
BEANアノテーションで指定しているexamples.jsf.dto.DepartmentDtoはexamples.jsf.entity.Departmentのサブクラスで、
このexamples.jsf.entity.DepartmentでTABLEアノテーションでDEPTテーブルとの関連付けを行っています。
public class Department implements Serializable {
public static final String TABLE = "DEPT";
:
}
これら2つのDAOはapp.diconでの設定によりS2Containerへ登録されています。
DAO自動登録で解説したようにDAOにはdaoInterceptorChainが指定されています。
daoInterceptorChainの中のdao.interceptorによって、EmployeeDtoDao・DepartmentDtoDaoはインタフェースに対して
実装クラスが自動的に生成されます。
EmployeeSearchInitActionImpl#initialize()のメソッドでは、
EmployeeLogicImpl#getAllDepartments()の戻り値(DepartmentDtoのList)をプロパティdepartmentDtoListにセットします。
getterメソッドとしてgetDepartmentDtoListがあるので、このプロパティが自動的にrequestスコープの属性として保存されます。
そのためセレクトタグのitem属性のdepartmentDtoListにセットされます。
public class EmployeeSearchInitActionImpl implements EmployeeSearchInitAction {
:
public String initialize() {
departmentDtoList = employeeLogic.getAllDepartments();
return null;
}
}
この画面(employeeSearch.html)の各入出力用のタグを見ていきましょう。
各入出力用のタグはJavaBeans(employeeSearchDto)のプロパティを連動させています。
EmployeeNoのタグは次のようになっています。
<tr>
<td class="label">EmployeeNo</td>
<td><input type="text" id="empno"
m:value="#{employeeSearchDto.empno}"
class="right">
<span m:inject="s:validator" m:binding="#{empnoRangeValidator}"/>
</input>
<span m:inject="h:message" m:for="empno" m:class="error"/>
</td>
</tr>
このvalidatorタグですがサンプルのvalidator.htmlと同じようにvalidatorタグのbinding属性でempnoRangeValidatorを指定しています。このバリデータは、examples/jsf/dicon/allvalidator.diconで次のように定義されています。
<component name="empnoRangeValidator" class="javax.faces.validator.LongRangeValidator">
<property name="maximum">10000</property>
</component>
EmployeeName, Job, Managerも特に変わった設定はありません。HireDateではvalidatorが無いのにmessageタグが用意されています。
<tr>
<td class="label">HireDate</td>
<td><input id="fromHiredate" type="text"
m:value="#{employeeSearchDto.fromHiredate}"/>
<span m:inject="h:message" m:for="fromHiredate" m:class="error"/>~
<input id="toHiredate" type="text"
m:value="#{employeeSearchDto.toHiredate}"/>
<span m:inject="h:message" m:for="toHiredate" m:class="error"/>
</td>
</tr>
これはJSFの標準機能でJavaBeanのプロパティの型をString型と相互に変換する標準のコンバータを適用してくれます。
標準コンバータの呼び出しでもvalidationが行われるためコンバータが解釈できなければエラーメッセージがセットされます。
Salaryも同様に標準のコンバータが適応されます。
ただし、このs2jsf-exampleではJavaBeanのプロパティの型がjava.util.Dateの場合はorg.seasar.jsf.convert.S2DateTimeConverterを使用するようにしてあります。
この定義はfaces-config.xmlで次のように定義してあります。
<converter>
<converter-for-class>java.util.Date</converter-for-class>
<converter-class>org.seasar.jsf.convert.S2DateTimeConverter</converter-class>
</converter>
次にcreate・searchボタンを見ていきましょう。
action属性でメソッドを呼び出しています(MethodBinding)。
onclick属性のlocation.href='employeeEdit.html'の部分はプレビュー用です。
<input type="button" value="create"
m:action="#{employeeSearchAction.goEditForCreate}"
onclick="location.href='employeeEdit.html'"/>
<input type="button" value="search"
m:action="#{employeeSearchAction.checkSearchCount}"
onclick="location.href='employeeList.html'"/>
createボタンのaction属性に記述されているemployeeSearchActionを見ていきましょう。
employeeSearchActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeSearchActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeSearchActionImplクラスを見ていきましょう。
examples.jsf.action.impl.EmployeeSearchActionImplは以下のプロパティを持っています。
public class EmployeeSearchActionImpl implements EmployeeSearchAction {
private EmployeeLogic employeeLogic;
private EmployeeSearchDto employeeSearchDto;
private ProcessModeDto processModeDto;
:
}
examples.jsf.dto.ProcessModeDtoは状態を表すDTOです。
DTO自動登録設定によって、S2Containerに登録されています。
ProcessModeDtoはプロパティとしてprocessModeがあるだけのクラスです。
そのインスタンスがsessionで管理されています。
app.diconではrequestスコープへ登録するよう設定されていますが、
ProcessModeDtoのCOMPONENTアノテーションでapp.diconの設定を上書きしています。
public class ProcessModeDto {
public static final String COMPONENT = "instance = session";
:
それでは、createボタンが押されたときに実行されるexamples.jsf.action.impl.EmployeeSearchActionImplのgoEditForCreate()を見てみましょう。
public String goEditForCreate() {
processModeDto.setProcessMode(Constants.CREATE_MODE);
return "employeeEdit";
}
Constants(examples.jsf.common.Constants)は以下のように処理の状態を表す定数を定義しただけのインタフェースです。
public interface Constants {
public int CREATE_MODE = 1;
public int UPDATE_MODE = 2;
public int DELETE_MODE = 3;
public int REFER_MODE = 4;
}
ProcessModeDtoに「CREATE_MODE」という状態が記録され、最後に"employeeEdit"が返されます。
faces-config.xmlを見ると、employeeEditは/employee/employeeEdit.htmlと定義されています。
<navigation-rule>
<navigation-case>
<from-outcome>employeeEdit</from-outcome>
<to-view-id>/employee/employeeEdit.html</to-view-id>
</navigation-case>
</navigation-rule>
/employee/employeeEdit.htmlを見てみましょう。見ていく順番は以下のようになります。
<html xmlns:m="http://www.seasar.org/maya"
m:action="#{employeeEditInitAction.initialize}"
m:extends="/WEB-INF/layout/layout.html">
2行目のaction属性でページの初期処理をするメソッドを指定しています。
employeeEditInitActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeEditInitActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeEditInitActionImplを見ていきましょう。
examples.jsf.action.impl.EmployeeEditInitActionImplクラスは以下のプロパティを持っています。
public class EmployeeEditInitActionImpl implements EmployeeEditInitAction {
private EmployeeLogic employeeLogic;
private Integer empno;
private ProcessModeDto processModeDto;
private EmployeeDto employeeDto;
private List departmentDtoList;
:
}
empnoは単なるプロパティで、ProcessModeDtoは前に書いたようにプログラムの状態管理です。
最後のdepartmentDtoListはsetterメソッドが無いので、EmployeeEditInitActionImpl生成時点では何も行われません。
action属性で指定されたinitialize()の中でセットされます。そのinitialize()は次のようになっています。
getAllDepartments()でテーブルDEPTの全レコードを取得しています。
次のif文では、現在の状態がUPDATE_MODE(編集状態)ならempnoをキーにテーブルEMPを検索した結果をemployeeDtoへ設定します。
これをHTMLファイルでValueBindingすることにより、値が自動的に画面表示されます。
今回はCREATE_MODEなので検索は行われずemployeeDtoは空になります。
public String initialize() {
departmentDtoList = employeeLogic.getAllDepartments();
if (processModeDto.getProcessMode() == Constants.UPDATE_MODE) {
employeeDto = employeeLogic.getEmployeeDto(empno);
}
return null;
}
この画面(employeeEdit.html)の各入出力用のタグを見ていきましょう。
各入出力用のタグはJavaBeans(employeeDto)のプロパティを連動させています。
画面に表示させる処理モードの表示方法は次のようになります。
<span m:value="Create"
m:rendered="#{processModeDto.processMode == 1}"
m:class="display" class="notdisplay">
Create
</span>
<span m:value="Edit"
m:rendered="#{processModeDto.processMode == 2}">
Edit
</span>
renderedは条件分岐で以下のように設定します。
<span m:value="値1" m:rendered="条件式1">値2</span>
<span m:rendered="条件式2">値3</span>
上の場合、このspanタグの評価結果は
- 条件式1がtrueなら値1。falseなら何も返さない。値2はプレビュー用。
- 条件式2がtrueなら値3。falseなら何も返さない。値3プレビューでも使われる。
値3にはまた別なspanタグを書いたりしてネストすることもできます。
最初のspanタグはprocessModeDto.processMode == 1ならvalue属性の"Create"になります。
spanタグで囲まれている方の"Create"はプレビュー用です。
spanタグのm:classはrendered = true(つまりS2JSF環境で実行して条件が成立したとき)使われるCSSのclassで、
ただのclass(notdisplay)はプレビュー用のものとなります。
プレビュー用のclass(notdisplay)はCreateを表示させないようにしています。
プレビューで見たときCreateとEditの両方が表示されてしまうためです。
またm:class="display"を記述しておかないと、S2JSF環境で実行した場合にプレビュー用のclass(notdisplay)が適応されて表示されなくなってしまいます。
次に出てくるhiddenタグを見てみましょう。
<input type="hidden" m:value="#{employeeDto.versionNo}"
m:rendered="#{processModeDto.processMode == 2}"/>
<input type="hidden" m:value="#{employeeDto.empno}"
m:rendered="#{processModeDto.processMode == 2}"/>
これもrenderedを使用してUPDATE_MODEの時は、これらの値をhiddenで保持します。
次にEmployeeNo(主キーempno)用のフィールドを見ていきましょう。
<td class="label">EmployeeNo</td>
<td>
<input id="empno" type="text"
m:value="#{employeeDto.empno}"
m:class="display right"
class="notdisplay"
m:required="true"
m:rendered="#{processModeDto.processMode == 1}"/>
<span m:inject="h:message" m:for="empno" m:class="error"
m:rendered="#{processModeDto.processMode == 1}"/>
<div class="right" m:rendered="#{processModeDto.processMode == 2}">
<span m:value="#{employeeDto.empno}">7788</span>
</div>
</td>
このinputタグはrenderedでCREATE_MODEの時だけ生成されます。またrequired属性がtrueなので入力必須となります。messageも同様にCREATE_MODEの時だけ生成されるようになっています。UPDATE_MODEで生成されるのは、その後のdivタグの部分となります。
EmployeeName、Job、Manager、HireDate、Salary、Commissionの各フィールドはemployeeDtoを連動するようになっています。
<td class="label">EmployeeName</td>
<td><input type="text" m:value="#{employeeDto.ename}" value="SCOTT"/></td>
CREATE_MODEの場合、各プロパティには何もセットされていない(デフォルト値)。 UPDATE_MODEの場合、該当EMPレコードの内容が表示されるようになっています。何かが表示されるのは、UPDATE_MODEのときだけとなります。
employeeEdit.htmlの残りボタンを見ていきましょう。
<input type="button" value="confirm" m:action="#{employeeEditAction.goConfirm}"
onclick="location.href='employeeConfirm.html'"/>
<input type="button" value="previous"
m:action="#{employeeEditAction.goPrevious}"
m:immediate="true"
onclick="location.href='employeeList.html'"/>
immediate="true"を指定することでバリデーションをかけないでページ遷移が可能となります。
action属性のemployeeEditActionを見ていきましょう。
employeeEditActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeEditActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeEditActionImplクラスを見ていきましょう。
examples.jsf.action.impl.EmployeeEditActionImplは以下のプロパティを持っています。
public class EmployeeEditActionImpl implements EmployeeEditAction {
private EmployeeLogic employeeLogic;
private ProcessModeDto processModeDto;
private EmployeeDto employeeDto;
:
}
EmployeeLogicはテーブルEMPを操作するインタフェース。
その実装クラスEmployeeLogicImplがセットされます。
ProcessModeDtoはプログラムの状態を管理するDTOです。
EmployeeDtoはemployeeEdit.htmlでは各フィールドをemployeeDtoのプロパティにValueBindingしていて、
そのbindされたemployeeDtoがrequestスコープに保存されています。
それではボタンのMethodbindingのemployeeEditAction.goPreviousから見てみましょう。
public String goPrevious() {
switch (processModeDto.getProcessMode()) {
case Constants.CREATE_MODE :
return "employeeSearch";
case Constants.UPDATE_MODE :
return "employeeList";
default:
return null;
}
}
action属性で指定されているgoPrevious()は、現在の状態に合わせて戻り先を決めているだけです。
またimmediate="true"を指定していたので入力内容を反映せずにページ遷移が可能となります。
残りのMethodbindingのemployeeEditAction.goConfirm()は次のようになっています。
public String goConfirm() {
if (processModeDto.getProcessMode() == Constants.CREATE_MODE
&& employeeLogic.existEmployee(employeeDto.getEmpno())) {
throw new EmployeeAlreadyExistRuntimeException(employeeDto
.getEmpno().intValue());
}
return "employeeConfirm";
}
まずexistEmployee()はEmployeeLogicの以下のメソッドを呼んでいます。
public boolean existEmployee(Integer empno) {
return employeeDtoDao.getEmployeeDto(empno) != null;
}
引数empnoをキーにテーブルEMPを検索し、該当レコードをjava.util.Listに格納して、この戻り値がnullでない場合trueを返します。
呼び出し元のif文では、これを使ってCREATE_MODEで同じempnoを持つレコードがテーブルEMPにあれば例外EmployeeAlreadyExistRuntimeExceptionをスローしています。
EmployeeAlreadyExistRuntimeExceptionはjava.lang.RuntimeExceptionのサブクラス(正確には間にexamples.jsf.exceptionAppRuntimeExceptionが入る)なのでthrowsを書く必要がありません。
結果「従業員(xxxx)は既に存在します」というメッセージとして表示されます。
これはこのemployeeEditActionにactionInterceptorChainが適用されるからです。
empnoが重複していなければ"employeeConfirm"が返され、/employee/employeeConfirm.htmlに遷移します。
employeeSearch.htmlでcreateボタンが押された後に表示されるemployee/employeeConfirm.htmlを見ていきましょう。
見ていく順番は以下のようになります。
01:<html xmlns:m="http://www.seasar.org/maya"
02: m:action="#{employeeConfirmInitAction.initialize}"
03: m:extends="/WEB-INF/layout/layout.html">
2行目のaction属性でページを初期化するメソッドを指定しています。
employeeConfirmInitActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeConfirmInitActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeConfirmInitActionImplを見ていきましょう。
examples.jsf.action.impl.EmployeeConfirmInitActionImplのプロパティは以下のようになっています。
public class EmployeeConfirmInitActionImpl implements EmployeeConfirmInitAction {
private EmployeeLogic employeeLogic;
private EmployeeDto employeeDto;
:
}
EmployeeLogicはテーブルEMPを操作するインタフェース定義で、その実装クラス(EmployeeLogicImp)がセットされます。
EmployeeDtoは、このemployeeConfirm.htmlの場合、前画面のemployeeEdit.htmlの各フィールドでValueBindingされた結果が入っています。
初期化処理(initialize())は次のようになっています。
public String initialize() {
if (employeeDto.getDeptno() != null) {
String dname = employeeLogic.getDname(employeeDto.getDeptno());
employeeDto.setDname(dname);
}
return null;
}
employeeDto.getDeptno()で返されるのは、employeeEdit.htmlのDepartmentコンボボックスで選択された値であり、それに対応する選択肢の名称(つまりdname)は一緒にValueBindingで取り出せないので、ここで取得しています。
それでは/employee/employeeConfirm.htmlのbody部分を見ていきましょう。
まず初めにrenderedが4つあります。このemployeeConfirm.htmlは全モード共通で使われる画面のためです。
<span m:value="Create"
m:rendered="#{processModeDto.processMode == 1}"
m:class="display" class="notdisplay">
Create
</span>
<span m:value="Edit"
m:rendered="#{processModeDto.processMode == 2}">
Edit
</span>
<span m:value="Delete"
m:rendered="#{processModeDto.processMode == 3}"
m:class="display" class="notdisplay">
Delete
</span>
<span m:value="Inquire"
m:rendered="#{processModeDto.processMode == 4}"
m:class="display" class="notdisplay">
Inquire
</span>
その次に以下のようにhiddenタグが設定されています。
<input type="hidden" m:value="#{employeeDto.versionNo}"/>
<input type="hidden" m:value="#{employeeDto.empno}"/>
<input type="hidden" m:value="#{employeeDto.ename}"/>
<input type="hidden" m:value="#{employeeDto.job}"/>
<input type="hidden" m:value="#{employeeDto.mgr}"/>
<input type="hidden" m:value="#{employeeDto.hiredate}"/>
<input type="hidden" m:value="#{employeeDto.sal}"/>
<input type="hidden" m:value="#{employeeDto.comm}"/>
<input type="hidden" m:value="#{employeeDto.deptno}"/>
<input type="hidden" m:value="#{employeeDto.dname}"/>
このemployeeConfirm.htmlではemployeeDtoにセットされている値、つまり前の画面で入力された値を
<td class="label">EmployeeNo</td>
<td class="right"><span m:value="#{employeeDto.empno}">7788</span></td>
といった単なる文字列で表示するため、次の画面に(employeeDtoの内容を)渡せないのでhiddenを使ってValueBindingしています。
最後にボタンの定義を見ていきましょう。
<input type="button" value="store"
m:action="#{employeeConfirmAction.store}"
m:rendered="#{processModeDto.processMode != 4}"
onclick="location.href='employeeSearch.html'"/>
<input type="button" value="previous"
m:action="#{employeeConfirmAction.goPrevious}"
onclick="location.href='employeeEdit.html'"/>
storeはrenderedの条件式がprocessModeDto.processMode != 4 なので、CREATE_MODE, UPDATE_MODE, DELETE_MODEのとき表示されます。
action属性のemployeeConfirmActionを見ていきましょう。
employeeConfirmActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeConfirmActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeConfirmActionImplクラスを見ていきましょう。
EmployeeConfirmActionImplは以下のプロパティを持っています。
public class EmployeeConfirmActionImpl implements EmployeeConfirmAction {
private EmployeeLogic employeeLogic;
private ProcessModeDto processModeDto;
private EmployeeDto employeeDto;
:
}
EmployeeLogicはインタフェースで実際はEmployeeLogicImplというテーブルEMPに対する操作を実装したクラスのインスタンスがセットされます。ProcessModeDtoはプログラムの状態を管理用。
EmployeeDtoには前画面から渡されたデータが入っています。
storeボタンが押されたとき呼び出されるEmployeeLogic#store()を見てみましょう。
public String store() {
switch (processModeDto.getProcessMode()) {
case Constants.CREATE_MODE :
employeeLogic.insert(employeeDto);
break;
case Constants.UPDATE_MODE :
employeeLogic.update(employeeDto);
break;
case Constants.DELETE_MODE :
employeeLogic.delete(employeeDto);
break;
}
return "employeeSearch";
}
現在の処理モードに合わせてメソッドを呼び分けています。EmployeeLogicImpl側のメソッド定義は次のようになっています。
public void insert(EmployeeDto dto) {
employeeDtoDao.insert(dto);
}
public void update(EmployeeDto dto) {
employeeDtoDao.update(dto);
}
public void delete(EmployeeDto dto) {
employeeDtoDao.delete(dto);
}
EmployeeDtoDao経由でS2Daoを使用して処理を行っています。
public void insert(EmployeeDto dto);
public void update(EmployeeDto dto);
public void delete(EmployeeDto dto);
これだけでS2Daoは更新系SQLを自動的に生成して実行してくれます。
previousボタンで呼び出されるgoPrevious()は単に次の表示ページを決めるための文字列を返しているだけです。
public String goPrevious() {
switch (processModeDto.getProcessMode()) {
case Constants.CREATE_MODE :
case Constants.UPDATE_MODE :
return "employeeEdit";
default:
return "employeeList";
}
}
いままでがemployeeSearch.htmlのcreateボタンを押されて始まる一連の処理となります。
次は同じくemployeeSearch.htmlにあるsearchボタンが押されたときの処理を見てみましょう。
<input type="button" value="search"
m:action="#{employeeSearchAction.checkSearchCount}"
onclick="location.href='employeeList.html'"/>
employeeSearchActionについてはこちらを参照してください。このボタンが押されるとEmployeeSearchActionImpl#checkSearchCount()が呼び出されます。
public String checkSearchCount() {
if (employeeLogic.getSearchCount(employeeSearchDto) == 0) {
throw new BadCriteriaRuntimeException();
}
return "employeeList";
}
先に該当レコード数をチェックします。この場合employeeLogicにはテーブルEMPを操作するインタフェースEmployeeLogicを実装したクラスEmployeeLogicImpのインスタンスがセットされていて、employeeSearchDtoにはemployeeSearch.htmlへ入力された結果がValueBindingされているいます。getSearchCount()は次のようにemployeeDtoDaoのgetSearchCount()を呼んでいるだけです。
public int getSearchCount(EmployeeSearchDto dto) {
return employeeDtoDao.getSearchCount(dto);
}
getSearchCount()でDTOを検索条件として該当レコード数を取得しています。この結果でcheckSearchCount()は該当レコードがなければ例外BadCriteriaRuntimeExceptionをスローします。 該当あれば"employeeList"を返します。この結果、/employee/employeeList.htmlへ遷移します。
/employee/employeeList.htmlはemployeeSearch.htmlへ入力された条件に該当するレコードをリスト表示する画面です。
見ていく順番は以下のようになります。
01:<html xmlns:m="http://www.seasar.org/maya"
02: m:action="#{employeeListInitAction.initialize}"
03: m:extends="/WEB-INF/layout/layout.html">
2行目のaction属性でページを初期化するメソッドを指定しています。
employeeListInitActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeListInitActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeListInitActionImplを見ていきましょう。
定義されているプロパティは次の通りです。
public class EmployeeListInitActionImpl implements EmployeeListInitAction {
private EmployeeSearchDto employeeSearchDto;
private EmployeeLogic employeeLogic;
private List employeeDtoList;
:
}
employeeSearchDtoは前画面のemployeeSearch.htmlに入力された内容を保持するもので、
sessionスコープで管理されています(examples.jsf.dto.EmployeeSearchDtoクラスのCOMPONENTアノテーションを参照)。
それがsetterメソッドsetEmployeeSearchDto()経由でここにセットされます。
employeeLogicにはテーブルEMPを操作するインタフェースEmployeeLogicを実装したクラスEmployeeLogicImpのインスタンスがセットされます。
employeeDtoListは、initialize()の中でセットされます。そのinitialize()は次のようになっています。
public String initialize() {
employeeDtoList = employeeLogic.searchEmployeeDtoList(employeeSearchDto);
return null;
}
EmployeeLogicImp#searchEmployeeDtoList()もEmployeeDtoDaoの同名メソッドを呼び出すだけです。
public List searchEmployeeDtoList(EmployeeSearchDto dto) {
return employeeDtoDao.searchEmployeeDtoList(dto);
}
この結果がemployeeDtoListにセットされます。そのemployeeDtoListはgetterメソッドとしてgetEmployeeDtoListがあるので、このプロパティが自動的にrequestスコープの属性として保管されます。
employeeList.htmlの画面組み立てを見ていきましょう。
forEachタグを見てみましょう。
<span m:inject="s:forEach"
m:items="#{employeeDtoList}"
m:var="e"
m:varIndex="i">
:
</span>
サンプルのforEachタグを見てもらえば分かるように、この場合はemployeeDtoList(初期化処理で作成されたテーブルEMPの検索結果)の各要素をeとし、それが何番目の要素かはiで表す、ということになります。実際に繰り返されるのはこのspanタグにはさまれたtrタグです。そのtrタグは以下のようになっています。
<tr class="evenRow" m:class="#{i % 2 == 0 ? 'evenRow' : 'oddRow'}">
<td class="right"><span m:value="#{e.empno}">7369</span></td>
<td><span m:value="#{e.ename}">SMITH</span></td>
<td><span m:value="#{e.job}">CLERK</span></td>
<td class="right"><span m:value="#{e.mgr}">7902</span></td>
<td><span m:value="#{e.hiredate}">1980/12/07</span></td>
<td class="right"><span m:value="#{e.sal}">800</span></td>
<td class="right"><span m:value="#{e.comm}"/></td>
<td><span m:value="#{e.dname}">RESEARCH</span></td>
<td><a href="employeeEdit.html" m:action="#{employeeListAction.goNext}">Edit
<span m:inject="f:param" m:name="empno" m:value="#{e.empno}"/>
<span m:inject="f:param" m:name="processMode" m:value="2"/>
</a>
<a href="employeeConfirm.html" m:action="#{employeeListAction.goNext}">Delete
<span m:inject="f:param" m:name="empno" m:value="#{e.empno}"/>
<span m:inject="f:param" m:name="processMode" m:value="3"/>
</a>
<a href="employeeConfirm.html" m:action="#{employeeListAction.goNext}">Inquire
<span m:inject="f:param" m:name="empno" m:value="#{e.empno}"/>
<span m:inject="f:param" m:name="processMode" m:value="4"/>
</a>
</td>
</tr>
</span>
以下のようtrタグのm:classに条件式を使用して背景を変えることもできます。
<tr class="evenRow" m:class="#{i % 2 == 0 ? 'evenRow' : 'oddRow'}">
各行の右端には"Edit","Delete","Inquire"のリンクでセットするパラメータが違うだけで、基本的には同じです。
Editは次のようになっています。
<a href="employeeEdit.html" m:action="#{employeeListAction.goNext}">Edit
<span m:inject="f:param" m:name="empno" m:value="#{e.empno}"/>
<span m:inject="f:param" m:name="processMode" m:value="2"/>
</a>
action属性のemployeeListActionを見ていきましょう。
employeeListActionは、Action自動登録設定によってS2Containerに登録されています。
- クラスはexamples.jsf.action.impl.EmployeeListActionImplです
- instanceモードはrequestです
- アスペクトとしてactionInterceptorChainが登録されています
examples.jsf.action.impl.EmployeeListActionImplクラスを見ていきましょう。
定義されてるプロパティは以下の通りです。
public class EmployeeListActionImpl implements EmployeeListAction {
private int processMode;
private ProcessModeDto processModeDto;
private Integer empno;
private EmployeeLogic employeeLogic;
private EmployeeDto employeeDto;
:
}
processModeとempnoはaタグの子タグでとして指定されたf:paramタグのパラメータ名に一致してsetterメソッドがあるので、S2JSFがMethodBindingで指定したgoNext()を実行する前にパラメータ値をセットします。
goNext()は、processModeを使って遷移先を決定します。
public String goNext() {
processModeDto.setProcessMode(processMode);
switch (processModeDto.getProcessMode()) {
case Constants.UPDATE_MODE :
return "employeeEdit";
default :
employeeDto = employeeLogic.getEmployeeDto(empno);
return "employeeConfirm";
}
}
processModeDtoに選択されたモードをセットして、UPDATE_MODE(Edit)のときは/employee/employeeEdit.html(=employeeEdit) それ以外は/employee/employeeConfirm.html(=employeeConfirm) へ遷移させます。
|