Teedaのサンプルアプリを読んでみる(7)

次はToDo一覧から[登録]で遷移するあたり。このボタンのIDは"go"で始まる"goTodoEdit"なので"doHogeHoge"みたいね対応するメソッドは無く、

  1. 対応するPageクラスのプロパティを更新
  2. Validator起動して入力値チェック
  3. 次画面へ遷移

となる。次画面決定のルールは、IDから"go"をとって".html"を付けたもの、ただし先頭は小文字にする。よってこの場合はtodoEdit.htmlがターゲットとなる。別アプリケーションに飛ばす場合は、パッケージ名を_(アンダースコア)で区切って指定することになるけど、詳細はJava Expert#01参照。

さてここまでTodoListPageを見てきたけど、残りはdoCreate()位かな。これTakeOverアノテーションも指定されているけど、多分どこからも呼ばれることはない。つまり機能していないと思われ。

	@TakeOver(properties = "crudType")
	public String doCreate() {
		setCrudType(CrudType.CREATE);
		return "todoEdit";
	}

ロジックから推測すると、todoEdit.htmlを新規(create)と編集(update)で共有するためどちらのモードで呼び出したかフラグをセットする、ということなんだろう。しかしtodoList.htmlの[登録]はonClickが、

document.listForm.crudType.value='0';

となっていてhidden項目のcrudTypeに'0'をセットする。一方編集はリンクが

href="todoEdit.html?fixed_crudType=2&id=id"

となっているからcrudTypeに2がセットされる("fixed_"があると右辺の値はそのままになるため)。いずれもidは"goTodoEdit"なのでtodoEdit.htmlに対応するPageクラスTodoEditPageのプロパティcrudTypeにその内容がセットされる。よって↑は不要でしょ(実際コメントにしても動作するみたいだし)。

さてTodoEditPageに行きますか。まずはinitialize()とprerender()。TodoListPageとは反対に、prerender()は実質何もしていなくて、初期化にかかわる処理はinitialize()に書かれている。そのinitialize()は

  • UPDATE(CrudType=2)なら、当該レコードを取得してDTOへ変換→自画面表示
  • 上記以外なら、単に自画面表示

といった感じ。(prerender()ではなく)initialize()でこれをやるのは、エラーとかで自画面を再表示するとき入力したデータが消える(というより、元のデータに戻っちゃう)のを防ぐためだよね~、なんて考えだんだけ、どうやらハズレ。この処理をそっくりprerender()へ移動させても、入力データが消えることはない。ということは、prerender()の後、ブラウザに入力された値を取得しているのか?そう考えると...。あ~そうか、画面を表示するたびにSELECT文を実行する必要ないもんね。初期表示の時だけでいいんだからinitialize()なんだ。納得(汗。

そのinitialize()で目に付くのは、編集モードのとき(CrudType=2)実行される次の1文。

	getTodoDxo().convert(data, this);

このdataはjp.co.gihyo.javaexpert.todo.entity.Todoのインスタンスで、以下によって取得されたテーブルTODOの1レコード分のデータ。

	Todo data = getTodoDao().selectById(getId());

TodoDao#selectById()については前に書いたけど、要は引数で渡されたidに一致するレコードを返すというもの。引数のgetId()で取得されるのは、todoList.htmlの編集リンクに埋め込まれているidの値。

href="todoEdit.html?fixed_crudType=2&id=id"

ブラウザにレンダリングされるHTMLでは、この"id=id"の左辺"id"と同じ名前のプロパティが(ForEachで使われている)todoItemsのエレメント(Todo)にあるので、右辺がその値に置換される。一方"fixed_"で始まっている場合、この書き換えは行われない。これも前に書いた

これでdataは確定。thisは自明だからパス。問題はconvert()って何?ってこと。getTodoDxo()が返すのはインタフェースjp.co.gihyo.javaexpert.todo.web.todo.TodoDxoを実装したクラスのインスタンス。クラス名の終わりが"Dxo"なのでこれを手がかりにSMART deployを見ると

MapとJavaBeans、JavaBeansとJavaBeansといったオブジェクト間のデータの相互変換を定義するインターフェースです。インターフェースにメソッドを書いておくだけで、モデル変換のロジックは、アスペクトが自動生成します。 実装部分を作る必要はありません。

とある。なるほど。で、この部分を処理するのはS2Dxoなのね。でもこのS2DxoってSeasarプロジェクトのプロダクト一覧に載って無いんだよね~。探すのに少々手間取った(汗。

で、肝心のインタフェースはどうなっているかというと...

public interface TodoDxo {

	public Todo convert(AbstractTodoPage src);
	
	public void convert(Todo src, AbstractTodoPage dest);
}

initialize()のconvert()は2つめの方。srcのプロパティでdestと同じ名前のものがあれば、destにsrcの値をセットしてくれる処理をS2Dxoが自動的に生成してくれる。要はこのメソッドを定義しておくだけで、テーブルTODOの検索結果をPageクラスへセットしてくれる。これ便利。だからPageにテーブルのカラム名と同じ名前のプロパティを持っていたわけね*1

*1:もちろん名前が異なる場合も、アノテーションで指示できるようになっている