ファイヤープロジェクト
一つのformで複数の操作ボタンを設置し,一つのアクションで受ける.
2004-06-16T23:30+09:00   matsu
一つのformで複数の操作ボタンを設置し,一つのアクションで受けるには,「どのボタンが押されたのか?」に基づいて処理する必要がある.Strutsにはこのようなパターンを想定したActionのサブクラスがある.
以下のような画面があったとする.
この画面で,二つのテキストボックスに値を入力し,「足す」または「引く」ボタンを押すと,次の画面で三つ目のテキストボックスに計算結果(和または差)が出力されるようなアプリケーションを考える.以下は「足す」ボタンを押した結果である(※).
以下は「引く」ボタンを押した結果である.
以下は「未定義」ボタンを押した結果である.
これらは本頁のサンプルの出力画面である.これらの画面は一つのフォームの中に三つのテキストフィールドと三つのボタンがある.したがって,Java Script等を使用しない場合,どのボタンを押しても一つのアクションに送信される.送信されたアクションのActionクラスでは,どのボタンが押されたかに応じて処理を行う必要がある.フォームのボタンは以下のようになっている.
<input type="submit" name="command" value="足す">
この場合,Strutsにより,ActinFormのcommandに"足す"が設定される.もっとも単純な解決方法は,この値を参照,チェックして処理を分岐させる方法である.
if (form.getCommand().equals() == "足す")
...
だがこれだとボタンに表示させるメッセージを変更する度に,JSPとActionを変更させる必要が生じる.
※ ここでボタンを押してもどうにもならない.念のため.
Actionクラスがorg.apache.struts.actions.DispatchActionを拡張している場合,submitボタンのvalueの値と同名のメソッドが,executeと同じパラメータで呼び出される.例えば
<input type="submit" name="command" value="add">
というボタンが押されれば,該当Actionクラス(extends DispatchAction)でユーザが定義した(※)
public ActionForward add(ActionMapping mapping,
                         ActionForm form,
                         HttpServletRequest request,
                         HttpServletResponse response)
       throws Exception
が呼ばれる.ただし,この仕組みを使用するにはstruts-config.xmlにて該当actionタグの属性に
parameter="command"
などと記述しなければならない.属性parameterの値はsubmitボタンのname属性の値(すなわちJSP上ではhtml:submitカスタムタグの属性propertyの値)と揃えなければならない.
※ 呼び出すメソッドが見付からなければunspedifiedメソッドが呼ばれる.
DispatchActionを使用した場合でもなお問題がある.submitボタンの属性valueの値をDispatchActionのサブクラスのメソッド名と想定して,そのメソッドを呼ぼうとするので,当然ながら,メソッド名として使える文字列しか,submitボタンの属性valueの値を使用できない.すなわちボタンに「足す」とか「引く」などと記述することはできない.この問題に対応できるものとして,org.apache.struts.actions.DispatchActionがある.これはsubmitボタンのvalueの値と呼び出すメソッド名の間にメッセージリソースをはさむことにより,submitボタンの属性valueの値の制約を無くす.まず以下のようなメッセージリソースファイルがあるとする.
button.cmd.plus=足す
button.cmd.minus=引く
button.cmd.unknown=未定義
error.cmd.unknown=未定義操作です.
これに対してJSPのsubmitボタンには以下のように記述する.
  <!-- 操作1 plus -->
  <html:submit property="command">
    <bean:message key="button.cmd.plus"/>
  </html:submit>
  <!-- 操作2 minus -->
  <html:submit property="command">
    <bean:message key="button.cmd.minus"/>
  </html:submit>
  <!-- 未定義操作 -->
  <html:submit property="command">
    <bean:message key="button.cmd.unknown"/>
  </html:submit>
html:submitの属性propertyの値はsubmitボタンのinputタグの属性nameの値となる.そしてhtml:submitの内容はsubmitボタンのinputタグのvalue値となる.これにbean:messageタグを使用してメッセージリソースのキーを指定している.これでJSP出力にはキーに紐付けられた値が出力される. ActionクラスであるLookupDispatchActionのサブクラスでは
protected Map getKeyMethodMap()
をオーバーライドする.返すMapには,
このメッセージリソースキーに対してこの名前のメソッド
という対応情報を格納する.もちろんその名前のメソッドも定義する.以上をまとめるとLookupDispatchActionを使用した場合の処理はおおよそ以下になる.
  1. (JSPでカスタムタグhtml:submitでメッセージリソースのキーを指定)
  2. JSP出力でキーに対応するメッセージリソースのメッセージが出力される.
  3. ユーザがsubmitボタンを押す(属性name,valueの値が送信される).
  4. Struts該当actionの属性parameterの値と送信されたname属性の値が一致するかをチェック(※).
  5. ActionクラスのgetKeyMethodMapでMapを取得
  6. 送信されたvalue属性の値に対応するメッセージリソース上のキーを使用して(複数ある場合は最初のものが使用される),Mapから実行すべきメソッドを特定,実行する.
  7. 実行すべきメソッドがMapから特定できなければunspecifiedが呼ばれる.
メッセージリソースのキーと値が1対1とは限らないというのは悩ましい問題かもしれない.また,Mapで特定したメソッドが見付からないという状況はバグである.洩れなくメソッドを実装すること.
※ 実際にはsubmitボタンの
属性nameの値=属性valueの値
というクエリがブラウザによって生成されているので,そのキー名のチェックになる.
LookupDispatchActionを使用して最初の問題を解決するサンプルを示す.まずstruts-config.xml(抜粋).
<struts-config>
  <form-beans>
    <form-bean      name="MyActionForm"
                    type="matsu.struts.sample.MyActionForm"/>
  </form-beans>

  <action-mappings>
    <action    path="/SomeActions"
               type="matsu.struts.sample.SomeActions"
	       scope="request"
	       name="MyActionForm"
	       parameter="command">
      <forward name="success"        path="/some_actions.jsp" />
    </action>
  </action-mappings>
</struts-config>
要素actionに属性parameterを付けるのを忘れない.次にActionForm.
要求のパラメータとして飛んで来るので,struts-config.xmlの要素actionの属性parameterの値のアクセサも用意しておく.
    public void setCommand (String val) {
	System.err.println("===setCommand called : " + val);
	this.method = val;
    }

    public String getCommand (){
	return method;
    }
set(/get)Parameterと属性parameterの属性名ではなく,属性値にもとづいてアクセサを作成する点に注意.次にActionクラス.
先述のように実装すべきは,getKeyMethodMapとunspecified(おそらくこのメソッド内は大抵エラー処理になる),それにMapから特定した結果呼び出すメソッド(ここではplusとminus)である.あとはJSPではgetKeyMethodMapで返すMapに基づいてボタンを作成する.
メッセージリソースファイルは先に示した.実行結果画面も先に示した.以下は実行時のcatalina.out.
===setCommand called : ??
=== plus called
===setCommand called : ??
=== minus called
===setCommand called : ???
=== unspecified called
なんか"?"と出ているが,これは次説のfilterによるものである(と思う).
本頁のサンプルをまとめてみた.これ.サンプルは
ant war
にてwarファイルができるので,それをデプロイする.環境によってはこれを実行する前に,
src/WEB-INF/
にStrutsのtldファイルを,
lib/
にStrutsのjarファイルを置く必要がある.実行時の注意を以下に示す(※).
native2ascii
サンプルでは
ant war
にてメッセージリソースをnative2asciiしている.こうしないとJSP出力で文字化けする.
フィルタの使用
リソースファイルはEUC_JPにて記述した.そして
ant war
にてnative2asciiされる.したがってフィルタを使用するなどしてブラウザから送信されたsubmitボタンのvalue値をEUC_JPにしなければ,実行メソッドの「Lookup」時にどれともマッチせずにunspecifiedが呼ばれてしまう.そこでサンプルのweb.xmlには以下を書いておいた.
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <filter>
    <filter-name>Encode to EUC_JP</filter-name>
    <filter-class>filters.SetCharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>EUC_JP</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>Encode to EUC_JP</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
...省略...
あとはwarファイル展開後のディレクトリに入って
mkdir -p  WEB-INF/classes/filters/
cp $CATALINA_HOME/webapps/examples/WEB-INF/classes/filters/SetCharacterEncodingFilter.class\
   WEB-INF/classes/filters/
などとする.とにかくTomcatのexamplesのWEB-INF/classes/filters/SetCharacterEncodingFilter.classをサンプルでも使用できるように準備する.
※ もっともこのサンプルやLookupDispActionに限った注意点ではない.
matsu(C)
Since 2002
Mail to matsu