[PlayFramework]フォームをJSONでPOSTし、バリデーションエラーをJSONで返す

## まえがき

今回作成したコードはgithubにあげております
・ https://github.com/agoetc/play-form-example

# 目標
ページ遷移なしでいい感じにバリデーションをかけたい
## バージョン
* sbt 1.2.8
* Scala 2.13.0
* PlayFramework 2.7.3

## フォームのPOST
今回はユーザーの登録を仮定して実装していきます。
まず、formで使用するcontrollerと、そのrouteを作成します。

```scala:HomeController.scala

@Singleton
class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {

  // フォームのあるページを表示するよ
  def index = Action {
    Ok(views.html.index())
  }

  // ここでjsonを受け取ってバリデーションかけたいよ
  def post = Action {
    Ok("")
  }

}
```

```:route
GET     /                           controllers.HomeController.index
POST    /post                       controllers.HomeController.post

GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)
```


そして、viewを書いていきます。
PlayFrameworkの初期設定ではCSRF対策が必須で、
フォームを使用する場合、トークンを明示的に渡さなくてはいけません。
helperでいい感じに渡せるので書いていきましょう

また、CSRFを使用するときはRequestHeaderが必要らしいので引数に入れましょう

```html:index.scala.html
@import helper._
@()(implicit requestHeader: RequestHeader)

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>

@form(CSRF(routes.HomeController.post())) {
    <div>
        <label for="name">なまえ</label>
        <input type="text" id="name" name="name" placeholder="なまえ">
    </div>
    <div>
        <label for="kana">かな</label>
        <input type="text" id="kana" name="kana" placeholder="ふりがな">
    </div>

    <div class="button">
        <button type="button">送信</button>
    </div>
}
```

<img width="370" alt="スクリーンショット 2018-05-30 20.42.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/151730/80879cbe-5a2c-93ca-4dea-a74a9801b7e4.png">
これでフォームを作成することができました。  
ページ遷移なしでの送信を行いたいので、
ajaxを使用するためにjQueryを入れています。
contentSecurityPolicyが有効になっているとjs使うの面倒くさいので無効にしておきます
（有効にしたまま使う方法がわからない…誰か氏〜〜）

```conf:conf/application.conf
play.filters.headers.contentSecurityPolicy = null
```

formの情報は`$('form').serializeArray()`で手に入れることができますが、　　

```json:望まない形式.json
[
  {
    "name":  "name",
    "value": "入力値"
  }, {
    "name":  "kana",
    "value": "入力値"
  }
]
```

という辛い形ですので自力で形式を変更していきます。　　
下記の記事を参考にさせていただきました(というかもろパクリ)ので
詳しくはこちらをお読みください。

　[【JavaScript】「formをjsonにしてpost」する。]
(https://qiita.com/hnkyi/items/3f2faf2848cc2de0cf8c)　　

```javascript:main.js
function parseJson(data) {
    var returnJson = {};
    for (idx = 0; idx < data.length; idx++) {
        returnJson[data[idx].name] = data[idx].value
    }
    return JSON.stringify(returnJson);
}

$(function() {
    $('button').on('click',function(){
        var data = parseJson($('form').serializeArray());

        $.post({
                 // フォーム内のactionを取得する
            url: $('form').attr('action'),
            data: data,
            contentType: 'application/json',
        }).then(
            //　ajax成功時
            results => console.log(results),
            error => alert("なにか問題が発生しました")
        );
    });
});
```

controller側で正しくjsonが送られていることを確認します

```scala:Homecontroller.scala
def post = Action { implicit request =>
    // Option[JsValue]
    val jsonOpt = request.body.asJson
    println(jsonOpt)

    Ok("")
}

```

ここまで記述し、フォームを送信すると
sbt consoleに`Some({"name":"入力値","kana":"入力値"})`が表示されていることが確認できました。


## バリデーション
case classとFormをご用意ください。

```scala:User.scala
package forms

import play.api.data.Form
import play.api.data.Forms._

object User {

  case class TestForm(name: String, kana: String)

  val testForm = Form(
    mapping(
      "name" -> nonEmptyText,
      "kana" -> nonEmptyText,
    )(TestForm.apply)(TestForm.unapply)
  )
}
```  

controllerでバリデーションをかけます。
Optionに包んでくれているので各自好きなようにしてください。


```scala:HomeController.scala
def post = Action { implicit request =>
    import forms.User.testForm

    val jsonOpt = request.body.asJson
    jsonOpt match {
      case Some(json) =>
        testForm.bind(json).fold(
          errors => BadRequest(errors.errorsAsJson),
          form => Ok
        )
      case None =>
        BadRequest
    }
  }
```

`errorsAsJson`でいい感じにerrorをJsonにしてくれます。
このまま実行すると
**An implicit MessagesProvider instance was not found.**と怒られますので対処していきます。

HomeControllerにMessagesActionBuilderをDIします
これをすることでいい感じにバリデーションメッセージが生成されるようになります

```scala:HomeController.scala

class HomeController @Inject()(
                               cc: ControllerComponents,
                               messagesAction: MessagesActionBuilder
                              ) 
extends AbstractController(cc) {

/* --------------------------------
 * 中略
 * --------------------------------
 */

  def post = messagesAction { implicit request =>
/* --------------------------------
 * 以下略
 * --------------------------------
 */

```

## 完成（仮）
これでとりあえず、バリデーションメッセージを取得できるコードが完成しました。
form作成時にnonEmptyTextを指定したので、
空のフォームを送ってみます。
javaScriptのconsoleに以下が表示されていれば成功ですやった〜〜〜〜
<img width="279" alt="スクリーンショット 2018-05-31 11.01.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/151730/ce1e10af-59ea-8151-1dc1-1af139b5acf3.png">


…英語じゃん？
バリデーションメッセージを指定します

## バリデーションメッセージの変更
デフォルトでは、`conf/messages`を変更すればいいです。
`conf/application.conf`で
`play.i18n.langs`をja等に設定している人は
`conf/messages.ja`のように作成しましょう

```:messages
error.required=入力してください
error.number=数字を入力してください
error.min={0}より大きい数字を入力ください
error.max={0}より小さい数字を入力ください
error.minLength={0}文字以上入力ください
error.maxLength={0}文字以下の文字数で入力ください
```

<img width="270" alt="スクリーンショット 2018-05-31 11.26.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/151730/487aaf8e-1af0-f948-a4a4-c34940baf4ff.png">

やったね〜〜〜

ついでにエラーをhtmlに生成します

## エラーの生成
```javascript:main.js 
function check(results) {
    $('span').remove();
    for (let key in results) {
        let element = document.getElementById(key);
        let span = document.createElement('span');
        span.innerHTML = results[key];
        // inputの上にエラーの生成
        element.parentNode.appendChild(span, element);
    }
//----------------------------
// 中略
//----------------------------

$(function() {
    $('button').on('click',function(){
        var data = parseJson($('form').serializeArray());
        $.post({
            url: $('form').attr('action'),
            data: data,
            contentType: 'application/json',
        }).then(
            results => $('span').remove(),
            error => check(error.responseJSON)
        );
    });
});

```




<img width="343" alt="スクリーンショット 2018-05-31 11.31.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/151730/9165d187-886a-61de-4d06-0231514f2521.png">


END:v: