お久しぶりです。伊藤(係長)です。
最近、PHPでの開発を一旦休業しまして、ScalaでPlayFramework(Play2)を
使ったWebアプリの開発を始めました。
というわけで今後は主にPlay2とかScalaとかの記事を書いていこうと思います。
最初は初級編という事で、ControllerとViewとを橋渡しする
Formのマッピングについて書きたいと思います。
調べればController側の実装はそれなりに出てくるのですが、
じゃあView側はどう書くの?とか思った経験があったので
その辺も考慮して実際に動くソースをだらだら挙げていきます。
1. Stringをマッピングする
■Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 |
object Test01Controller extends Controller { val test01Form = Form("name" -> text) def index = Action { Ok(views.html.test01("" ,test01Form)) } def submit = Action { implicit request => val name = test01Form.bindFromRequest.get Ok(name) } } |
■View
1 2 3 4 5 6 7 |
@(hogeForm: Form[String]) @import helper._ @helper.form(action = routes.Test01Controller.submit) { @inputText(hogeForm("name")) <input type="submit" value="送信"> } |
※まだmapping関数は登場しません。
2. Tupleをマッピングする
■Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package controllers import play.api._ import play.api.mvc._ import play.api.data._ import play.api.data.Forms._ object Test02Controller extends Controller { val test02Form = Form( tuple( "firstName" -> text, "lastName" -> text ) ) def index = Action { Ok(views.html.test02(test02Form)) } def submit = Action { implicit request => val (first, last) = test02Form.bindFromRequest.get Ok(last + first + "さん") } } |
■View
1 2 3 4 5 6 7 8 |
@(hogeForm: Form[(String,String)]) @import helper._ @helper.form(action = routes.Test02Controller.submit) { @inputText(hogeForm("firstName")) @inputText(hogeForm("lastName")) <input type="submit" value="送信"> } |
※まだmapping関数は登場しません。。
3. caseクラスをマッピングする(シンプル)
■Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package controllers import play.api._ import play.api.mvc._ import play.api.data._ import play.api.data.Forms._ case class Person(firstName:String, lastName:String, age:Int) //Formとマッピングするcase class object Test03Controller extends Controller { val test03Form = Form[Person]( mapping( "first" -> text, "last" -> text, "year" -> number ) (Person.apply) // FormからPersonを生成する関数を指定する (Person.unapply) // PersonからFormを生成する関数を指定する ) def index = Action { Ok(views.html.test03(test03Form)) } def submit = Action { implicit request => val person = test03Form.bindFromRequest.get Ok(person.lastName + person.firstName + person.age + "歳") } } |
■View
1 2 3 4 5 6 7 8 9 10 11 12 |
@(hogeForm: Form[Person]) @import helper._ @helper.form(action = routes.Test03Controller.submit) { @inputText(hogeForm("first")) @inputText(hogeForm("last")) @select( hogeForm("year"), options = Seq("1" -> "1歳", "10" -> "10歳", "20" -> "20歳") ) <input type="submit" value="送信"> } |
※やっとmapping関数が登場しました。この中でcaseクラスとFormのマッピングを定義します。
4. caseクラスをマッピングする(ネスト)
■Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package controllers import play.api._ import play.api.mvc._ import play.api.data._ import play.api.data.Forms._ case class Person2(firstName:String, lastName:String, profile:Profile) case class Profile(age:Int, address:String) object Test04Controller extends Controller { val test04Form = Form[Person2]( mapping( "first" -> text, "last" -> text, "profile" -> mapping( "year" -> number, "address" -> text ) (Profile.apply) // Formの"profile"部分からProfileを生成する関数を指定する (Profile.unapply) // ProfileからFormの"profile"部分を生成する関数を指定する ) (Person2.apply) // FormからPerson2を生成する関数を指定する (Person2.unapply) // Person2からFormを生成する関数を指定する ) def index = Action { Ok(views.html.test04(test04Form)) } def submit = Action { implicit request => val person2 = test04Form.bindFromRequest.get Ok(person2.lastName + person2.firstName + person2.profile.age + "歳" + "住所は" + person2.profile.address) } } |
■View
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@(hogeForm: Form[Person2]) @import helper._ @helper.form(action = routes.Test04Controller.submit) { @inputText(hogeForm("first")) @inputText(hogeForm("last")) @select( hogeForm("profile.year"), options = Seq("1" -> "1歳", "10" -> "10歳", "20" -> "20歳") ) @inputText(hogeForm("profile.address")) <input type="submit" value="送信"> } |
だんだんと複雑になってきましたね。ネストしている場合はmapping関数を
ネストして記述すればOKです。
それに合わせてView側も”profile.year”のようにピリオド区切りで指定します。
5. caseクラスをマッピングする(ネスト&Tupleネスト)
■Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package controllers import play.api._ import play.api.mvc._ import play.api.data._ import play.api.data.Forms._ case class Person3(firstName:String, lastName:String, profile:Profile2) case class Profile2(age:Int, address:String, telephone:String) object Test05Controller extends Controller { val test05Form = Form[Person3]( mapping( "first" -> text, "last" -> text, "profile" -> mapping( "year" -> number, "address" -> text, "telephone" -> tuple( "tel1" -> text, "tel2" -> text, "tel3" -> text ) ) {(f_year, f_address, f_tel) => Profile2(f_year, f_address, f_tel._1 + f_tel._2 + f_tel._3) } // Formの"profile"部分からProfile2を生成する関数を指定する {profile => Some(profile.age, profile.address, (profile.telephone.substring(2), profile.telephone.substring(4), profile.telephone.substring(4))) } // Profile2からFormの"profile"部分を生成する関数を指定する(telephone分割は適当です) ) (Person3.apply) // FormからPerson3を生成する関数を指定する (Person3.unapply) // Person3からFormを生成する関数を指定する ) def index = Action { Ok(views.html.test05(test05Form)) } def submit = Action { implicit request => val person3 = test05Form.bindFromRequest.get val name = person3.lastName + person3.firstName val age = person3.profile.age + "歳" val address = person3.profile.address val telephone = person3.profile.telephone Ok(name + age + address + telephone) } } |
■View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@(hogeForm: Form[Person3]) @import helper._ @helper.form(action = routes.Test05Controller.submit) { @inputText(hogeForm("first")) @inputText(hogeForm("last")) @select( hogeForm("profile.year"), options = Seq("1" -> "1歳", "10" -> "10歳", "20" -> "20歳") ) @inputText(hogeForm("profile.address")) @inputText(hogeForm("profile.telephone.tel1")) @inputText(hogeForm("profile.telephone.tel2")) @inputText(hogeForm("profile.telephone.tel3")) <input type="submit" value="送信"> } |
ここまで来るともう良くわからなくなってきましたね。
今まではProfileをマッピングする際にapply、unapply関数で済んでましたが、
すんなりcaseクラスとマッピングできない場合は、自前の関数を書く必要があります。
6. caseクラスをマッピングする(Seqあり)
若干トーンダウンしますが、Formに対応するView側の書き方がわかりづらかったので。
■Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package controllers import play.api._ import play.api.mvc._ import play.api.data._ import play.api.data.Forms._ case class Person4(name:String, families:Seq[(String, Int)]) object Test06Controller extends Controller { val test06Form = Form[Person4]( mapping( "name" -> text, "families" -> seq( tuple( "relationship" -> text, "age" -> number ) ) ) (Person4.apply) // FormからPerson4を生成する関数を指定する (Person4.unapply) // Person4からFormを生成する関数を指定する ) def index = Action { Ok(views.html.test06(test06Form)) } def submit = Action { implicit request => val person4 = test06Form.bindFromRequest.get val name = person4.name val number = person4.families.size Ok(name +"さんの家族は" + number + "人いるよ(※自分は除く)") } } |
■View
1 2 3 4 5 6 7 8 9 10 11 |
@(hogeForm: Form[Person4]) @import helper._ @helper.form(action = routes.Test06Controller.submit) { @inputText(hogeForm("name")) @inputText(hogeForm("families[0].relationship")) @inputText(hogeForm("families[0].age")) @inputText(hogeForm("families[1].relationship")) @inputText(hogeForm("families[1].age")) <input type="submit" value="送信"> } |
FormのField指定を”families[0].relationship”のように指定すれば良いみたいです。
ちなみに”families[].relationship”のようにインデックスなしで指定すると
うまく動いてくれません。。。
これで基本的な書き方は押さえられたと思います。
さらに複雑なマッピングをしなければならない場合は、組み合わせて書けばなんとかなると思います。
今回はマッピング型としてtextとnumberしか使用していませんが、他にも
nonEmptyTextとかlongNumberとかoptionalとかもあります。
また、verifyingを使った入力チェックも簡単にできるので、是非利用していきましょう。
元Javaプログラマ。現在はScala/PlayでWeb開発と、SwiftでiOSアプリ開発をしています。 Unitテストとか書いてる時が一番楽しかったりします。