Teedaのサンプルアプリを読んでみる(3)
Login成功で表示されるtodoList.htmlを読んでみる。
冒頭confirmFunc()という確認メッセージを表示すると思われるJavaScriptがある。なるほど。TODOの削除にチェックを入れて[削除]ボタンを押下するとこのメッセージが表示されて、[OK]を選択したときだけ本当に削除されんだな、なんて思ってしまうけど、これ機能してない。このconfirmFunc()はどこからも呼び出されてない。それにこのメッセージ内容に沿った確認処理はformのonSubmitイベントで起動することになると思うが、削除checkboxを各々確認してチェックが入れられているかどうか調べる必要があるだろうから、こんな単純ではないだろう。この辺はあとで時間があればいじってみることにして、とりあえずここでは見なかったことにしよう...。
formの中を見るとhidden(inputHiddenコンポーネント)が2つ。crudTypeはこのhtmlに対応するPageクラスTodoListPageがAbstractCrudPageから継承しているもの。crudという名前からして、ユーザの処理モード(Create, Read, Update, Delete)判別用に使っているんでしょう。
もうひとつのtodoItemsSaveは、id名が"Save"で終わっているのでItemsSave用のもの*1。これはこの後出てくるForEach用のデータを入力されたデータと一緒に次画面へ引き継ぐために使うもの。要はForEach用Listや配列をPageクラスで持っているだけでは、ValidationでNGになった場合、自画面へ戻ってその(エラーとなった入力データを)内容を表示できないので、その対策らしい。id名のルールは「ForEach用オブジェクト名+"Save"」なので、この場合todoItemsがForEachで指定されているはずだが、実際以下のdivタグでForEachによる繰り返しを指定している。
<div id="todoItems">
ポイントはこのid名が"Items"で終わっていること。これでTeedaが繰り返し項目として扱ってくれるんだけど、なんか地味だね。まぁそれは置いといて、このid名そのものが繰り返し対象オブジェクトの名前(プロパティ名)になる。TodoListPageではクラスTODOの配列として定義されている。もちろんList型でもOKなわけだ。でもTeedaのコンポーネント仕様をみると
値を更新する場合には配列、表示のみの場合はListを使う
となっている。
繰り返し表示は各項目のgetterメソッドがForEach対象オブジェクト(DtoまたはMap)に定義されていればそれを使って自動的に行われる。ただこのサンプルのようにコードを名称に変換して表示するといった加工をする場合は、それ用のgetterをPageクラスに定義しておく。たとえばtypeValの場合、getTypeVal()がTodoListPageにあるので、それが使われる。
public String getTypeVal() { return SelectHelper.getType(super.getTodoType()); }
このgetType()が1なら"仕事"といった文字列を返すことで変換をしている。todoDetail, stateValも同様。その次のimgタグはTeeda入門記やJava Expert#01にも解説が無いような...。でも動きは追える。totoList.htmlに定義されている
<img src="../../img/other.gif" id="priorityImg"/>
が、ブラウザに出力されるときには
<img id="priorityImg" src="/todo-manage/img/high.gif" />
のようになっている。priorityImgに対応するTodoListPageのgetPriorityImgSrc()を見ると、SelectHelper.getPriorityImg()を使ってpriorityに応じたgifファイルへのパスを取得して、それがsrcへセットされているよう。
直後のpriorityValは同じだからスルーしてlimitDate。これはjava.util.Date型のプロパティなのだがブラウザ上には年/月/日形式に編集されて表示されている。多分Conveterだろうと思ってTodoListPageを見るとそうだった。@Overrideでスーパークラスのメソッドをオーバーライドすることを宣言して(←なぜ必要かはわからない)、DateTimeConverterによる変換を指定している。年/月/日というかyyyy/MM/dd形式がデフォルトなんでしょう。
@Override @DateTimeConverter public Date getLimitDate() { return super.getLimitDate(); }
形式を変えるときは以下のようにそのパターンを記載する。たぶん@Overrideはいらないような気がするけど。
@Override @DateTimeConverter(pattern = "yyyy/MM/dd") public Date getLimitDate() { return super.getLimitDate(); }
ForEachで残っているのは編集画面へのリンク(aタグ)。todoList.htmlでは次のようになっている。
<a id="goTodoEdit" href="todoEdit.html?fixed_crudType=2&id=id" title="編集画面へ">編集</a>
aタグ、かつidが"go"で始まっているのでlinkコンポーネントとして扱われる。この結果、ブラウザには次のような感じで展開されたものが送られる。
<a id="goTodoEdit" href="todoEdit.html?crudType=2&id=1" title="編集画面へ" shape="rect">編集</a>
Teedaのlinkコンポーネントの仕様を見ると、hrefの中でkey=valueとなっている部分で、keyがPageクラスのプロパティ名と同じ場合、その値でvalue部分で置換される(上記のid=idがこれに相当)。しかし本当にそうだとすると、このidはTodoListPageのidが使われるため、すべての編集リンクで同じidが使われてしまう。これはどう考えても変だし実際にはそうならない。この場合は各行に対応するtodoItemsのエレメントの持つidの方が使われる、になるんだろう(そうは書かれて無いけど)。
あとkeyが"fixed_"で始まっている場合、この書き換えは行われない。なるほど。単にcrudType=hogeなどと書いてしまうと、この例のように固定の値を渡すことができなくなる。
あとこのForEach関係では、行の背景色を交互に変える設定がある。これにはDynamicPropertyが使われているんだが、Pageクラスの該当箇所は以下のgetRowStyle()。
public String getRowStyle() { if (getTodoIndex() % 2 == 0) { return "background-color: #FFE2B7;"; } return "background-color: #F2FFB7;"; }
このメソッド名は「"get" + id名 + idの属性名」を表していて、その属性をこのメソッドの戻り値へセットする。実際todoList.htmlにはForEach内のtrタグにrowというidが割り当てられていてstyle属性もある。よってこのstyle属性の値は上記メソッドの返すものになるという仕組み。やってることは簡単でその行が偶数行/奇数行によって異なるbackground-colorが設定される。ポイントは「その行」をどうやって判別するか?TeedaではForEachのidで指定した名称からItemsを取り除いたものにIndexを付加したプロパティを定義しておくと、そこに行番号(=インデックス)をセットしてくれるようになっている。この例の場合、そのidが"todoItems"なので"todoIndex"というプロパティがあればいいことになる。実際Page側はそうなっていて、getterメソッドであるgetTodoIndex()も定義されているので、上のようなコードが書ける、という具合。
ちなみに属性名が記述されていないとDynamicPropertyは働かないらしい。たとえば
<tr id="row" style="y">
のstyle="y"を削除すると、背景色(background-color)がなくなる。
ここまで読み進めるのに大分時間が掛かってしまったので、あと1つだけ。それは次のgoボタン。
<input type="submit" id="goTodoEdit" value="登録" class="submit" onclick="document.listForm.crudType.value='0';"/>
idが"go"で始まっているときは、残りの部分を遷移先として扱ってくれるので、todoEdit.htmlが次に表示されるんだろう。もっともその前にValidationが設定されていればそれが起動されるし、Pageクラスのプロパティも更新される。onclickでは最初に出てきたinputHiddenコンポーネントであるcrudTypeへこのタイミングで'0'をセットするため、JavaScriptを使っている。この結果、次のTodoEditにはこの値が引き継がれるのだろう。その辺はまたあとで。
*1:"SessionSave"で終わっていればItemsSessionSave用