playframeworkではフォームから値を受け取るのが非常に簡単です。inputタグのnameとアクションメソッドの引数名を一致させるだけで値を受け取ることができます。
あまり使わないテクニックは自分でも忘れてしまうので備忘も兼ねて書いておきます。
型ごとの書き方
数値型や文字列型
<input type="text" name="id" /> <input type="text" name="name" />
public static void action(Long id, String name) { ... }
プリミティブ型のときは何も考えずに受け取れる。受け取り側の型にパース出来ないときは null になる。
Date型
<input type="text" name="postAt" />
public static void action( @As("yyyy-MM-dd HH:mm:ss") Date postAt) { ... }
@play.data.binding.As
アノテーションで気軽に日付文字列をDate型として受け取ることができる。カレンダーピックアップなどのコントロールと組み合わせるとなお便利。
Enum型
列挙型の受け取りもフレームワークが行ってくれる。その際、Enum.valueOf()
でパースを試みるので、valueの値もそれに見合った値にしておく。
<input type="radio" name="status" value="${MyEnum.TO_DO}" /> <input type="radio" name="status" value="${MyEnum.IN_PROGRESS}" /> <input type="radio" name="status" value="${MyEnum.DONE}" />
public enum MyEnum { TO_DO, IN_PROGRESS, DONE }
public static void action(MyEnum status) { ... }
Map型
Map<String, String>
型の key と value として値を受け取ることもできる。パラメータが多かったり変化が大きいときなど、POJOクラスを用意するのが困難な時は有用。
<input type="text" name="test.name" /> <input type="text" name="test.address /> <input type="text" name="test.age" />
public static void action(Map test) { ... }
[ name: '...', address: '...', age: 'nn' ]
同じ name が使われていた場合はカンマ区切りで解決される。
POJOクラス
Mapの時と同様にドットで区切る。ネスト構造でも再帰的にバインドしてくれる。
<input type="text" name="user.name /> <input type="text" name="user.address" /> <input type="text" name="user.age" /> <input type="text" name="user.birthDay" />
public class User { public String name; public String address; public Long age; @As("yyyy-MM-dd") public Date birthDay; }
public static void action(User user) { ... }
JPAオブジェクトへのバインド
idが含まれるとfindById
で自動的にSELECTされ、そのオブジェクトに対してフォームから渡される値が設定される。そのまま save()
することもできる。
<input type="text" name="id" value="123"/> <input type="text" name="user.name /> <input type="text" name="user.address" /> <input type="text" name="user.age" /> <input type="text" name="user.birthDay" />
@Entity public class User extends Model { public String name; public String address; public Long age; @As("yyyy-MM-dd") public Date birthDay; }
public static void action(User user) { user.save(); }
余談だが、@javax.persistence.Version
を使った楽観的なバージョン管理をしている場合は、SELECTした時のVersion値をプロパティ値とは別で保持しており、故意にプロパティ値を変えてバージョンエラーを起こそうとしても起きない。これと同様の事象がバインドでも発生するので、画面からバージョン値は渡す場合は別変数として渡す必要がある。
JPAのリレーション
JPAオブジェクトをバインドする際、@javax.persistence.ManyToOne
などのリレーション系のアノテーションについても処理してくれる。name値の末尾に ".id"
を付けるのがポイント。
<input type="text" name="user.name" />
<select name="user.age.id" >
#{list models.Age.findAll() }
<option value="${_.id}">${_.generation}</option>
#{/list}
</select>
@Entity public class Age extends Model { public String generation; }
@Entity public class User extends Model { public String name; @ManyToOne public Age age; }
public static void action(User user) { ... }
カスタムバインダー
アクションメソッドの引数や、オブジェクトのプロパティに対して@play.data.binding.As
によって解決するためのタイプバインダーを指定することができる。
タイプバインダーは play.data.binding.TypeBinder
インタフェースを実装し、文字列からオブジェクトへどのように変換すればよいかを記述する。
作成した タイプバインダーに @play.data.binding.Global
を付与すると @As
で指定することなく等価的にタイプバインダーが適用される。
任意のオブジェクトにバインド
アクションメソッドの引数として書かない方法。任意のタイミングで任意の変数にバインドすることもできる。
<input type="text" name="user.name" /> <input type="text" name="user.address" />
User u = new User(); Binder.bindBean(params.getRootParamNode(), "user", u);
// JPAオブジェクトの場合 User u = User.findById(id); u.edit("user", params.all()); u.save();
タグの種類ごとの書き方
Radio Button
ラジオボタンの受け取りも用意。nameタグの値を統一するのがポイント。
初期選択状態を制御する場合はcheckedキーワードを任意で差し込む。
<input type="radio" name="reportType" value="1" /> <input type="radio" name="reportType" value="2" /> <input type="radio" name="reportType" value="3" />
public static void action(String reportType) { ... }
Checkbox
チェックボックスは配列型やコレクション型で受け取ることができる。nameタグの値を統一するのがポイント。
初期選択状態を制御する場合はcheckedキーワードを任意で差し込む。
<input type="checkbox" name="reportType" value="1" /> <input type="checkbox" name="reportType" value="2" /> <input type="checkbox" name="reportType" value="3" />
public static void action(Set<String> reportType) { ... } public static void action(String[] reportType) { ... }
Select
セレクトはラジオボタンやチェックボックスと同様です。単一選択ならラジオボタン方式、複数選択ならチェックボックス方式で受け取る。
<select name="ans1" > <option value="1">the flog</option> <option value="2">the rabbit</option> <option value="3">the bird</option> </select>
public static void action(String ans1) { ... }
<select name="ans2" multiple > <option value="1">Bike</option> <option value="2">Car</option> <option value="3">Train</option> </select>
public static void action(Set ans2) { ... } public static void action(String[] ans2) { ... }
Input type=”FILE”
form の enctype
を multipart/form-data
にして、input type="file"
にする。アップロードすると 一時フォルダ内にFileオブジェクトとして受け取ることができるが、リクエスト処理の終了と共に消えてしまう。必要ならコピーなど行う必要がある。
一時格納場所は ${play.tmp}/uploads/
配下。
<form method="POST" action="upload" enctype="multipart/form-data" > <input type="file" name="heno"/> </form
public static void upload(File heno) { heno.renameTo(new File("abc.bin"); }
JPAオブジェクトにplay.db.jpa.Blob
プロパティを置くと、エンティティに紐づいたファイルとして受け取ることができる。格納先は apllication.conf
で設定された ${attachments.path}
ディレクトリ。
<form method="POST" action="upload" enctype="multipart/form-data" > <input type="text" name="profile.name" /> <input type="file" name="profile.photo"/> </form
@Entity public class Profile extends Model { public String name; public Blob photo; }
public static void upload(Profile profile) { ... }
${attachments.path}
へのファイル保存は永続化とは同期しておらず、保存ポリシーはユーザに任されている(save()せず破棄したり、ロールバックしたり、あるいはdelete()しても削除されない)。アップロードされたファイルを勝手に消さない方向になっているのだろう。
なので、参照されていないファイルを削除するような非同期ジョブを定期的に動かすなどした方がいいかもしれない。
ついでにNettyで稼働している場合にマルチパートでアップロードすると一時ファイルが${play.tmp}
配下に作られる。こちらも自動で削除されないようなので適宜掃除することを検討されたし。