混乱する list Form
Play Framework で Form を List にできるのは大変便利なんですが、よく混乱する。
まずどう書くんだっけ的なところから始まり、
- checkbox の helper ってどうやって使うんだっけ?
- helper 使わない場合、name とかってどうすればいいんだっけ?
みたいな感じに毎回なります。要は慣れてないだけな気がするし、helper使うか用意しとけよって話なのかもしれない
で、ドキュメントとか色々な記事を見ながら実装してみてやっぱりハマったので今回の記事を書いてみた次第です。
list form のおさらい
helper を使うと結果、どういう html が生成されるか分かりづらいため、このおさらいでは生の html で例をあげます。
1. 一つのフィールドを list にするケース
<input type="checkbox" name="foo_ids[]" value="1">
<input type="checkbox" name="foo_ids[]" value="2">
<input type="checkbox" name="foo_ids[]" value="3">
case class FooForm(ids: List[Int])
val form = Form("foo_ids" -> list(number))
form.bindFromRequest.get // -> List[String]
2. ネストしたフォームを list にするケース
<input type="text" name="user[1].name" value="foo">
<input type="text" name="user[1].age" value="12">
<input type="text" name="user[2].name" value="bar">
<input type="text" name="user[2].age" value="20">
<input type="text" name="user[3].name" value="buzz">
<input type="text" name="user[3].age" value="31">
case class UsersForm(users: List[UserForm])
case class UserForm(
name: String,
age: Int
)
val form = Form {
mapping(
"users" -> list(mapping(
"name" -> nonEmptyText,
"age" -> number
)(UserForm.apply)(UserForm.unapply))
)(UsersForm.apply)(UsersForm.unapply)
}
form.bindFromRequest.get // -> UsersForm
で、本題
今回、下記のようなForm オブジェクトでデータを受け取りたい実装が出てきました。
case class FoosForm(foos: List[Foo])
case class FooForm(
id: Int,
value: Int
)
val form = Form {
mapping(
"foos" -> list(mapping(
"id" -> nonEmptyText,
"value" -> number
)(FooForm.apply)(FooForm.unapply))
)(FoosForm.apply)(FoosForm.unapply)
}
複数の checkbox にチェックを入れたもののみ、その checkbox の値(id
)とそれに対応する hidden の値(value
)をフォームに持ちたいという感じです。
HTMLにするとこんな感じ
case class Foo(id: Int, name: String, value: Int)
@(foos: List[Foo])
@for(foo <- foos) {
<input type="checkbox" name="foos[].id" value="@foo.id"> @foo.name
<input type="hidden" name="foos[].value" value="@foo.value">
}
どういう状態でハマったか
1. おさらい2.のように実装してみると…
name の []
の部分に unique な値を入れる感じですね。
case class Foo(id: Int, name: String, value: Int)
@(foos: List[Foo])
@for(foo <- foos) {
<input type="checkbox" name="foos[@foo.id].id" value="@foo.id"> @foo.name
<input type="hidden" name="foos[@foo.id].value" value="@foo.value">
}
こうすると、checkbox に check を入れてないものもデータとして渡ってきてしまうので、Form の required validation に引っかかってしまいました
2. おさらい1.のように実装してみると…
今度は name の []
の部分に unique な値を入れる感じです。
case class Foo(id: Int, name: String, value: Int)
@(foos: List[Foo])
@for(foo <- foos) {
<input type="checkbox" name="foos[@foo.id].id" value="@foo.id"> @foo.name
<input type="hidden" name="foos[@foo.id].value" value="@foo.value">
}
こうすると、今度はデータに何も渡ってきません。どうしたらよいものか…
どう解決したか
ハマったものを鑑みるに、name の []
に値が入っているものがデータとして送られてくるようなので、
Javascript を使って check が入ったものに対し []
に unique な値を入れるという処理にしました。
case class Foo(id: Int, name: String, value: Int)
@(foos: List[Foo])
@for(foo <- foos) {
<input type="checkbox" name="foos[].id" value="@foo.id" class="check_foo"> @foo.name
<input type="hidden" name="foos[].value" value="@foo.value" id="hidden_@foo.id">
}
<script type="text/javascript">
$('.check_foo').on('ifChecked', function() {
var fooId = $(this).val();
$(this).attr({'name': 'foos[' + fooId + '].id'});
var hidden = $('#hidden_' + fooId);
hidden.attr({'name': 'foos[' + fooId + '].value'});
}).on('ifUnchecked', function() {
var fooId = $(this).val();
$(this).attr({'name': 'foos[].id'});
var hidden = $('#hidden_' + fooId);
hidden.attr({'name': 'foos[].value'});
});
</script>
結論
PlayFramework に対するレベルが1上がった!
が、Form はやっぱり使いにくいというかイメージしにくい…