LoginSignup
10
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-05-31

まえがき

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

目標

ページ遷移なしでいい感じにバリデーションをかけたい

バージョン

  • sbt 1.2.8
  • Scala 2.13.0
  • PlayFramework 2.7.3

フォームのPOST

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

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が必要らしいので引数に入れましょう

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>
}

スクリーンショット 2018-05-30 20.42.41.png
これでフォームを作成することができました。

ページ遷移なしでの送信を行いたいので、
ajaxを使用するためにjQueryを入れています。
contentSecurityPolicyが有効になっているとjs使うの面倒くさいので無効にしておきます
(有効にしたまま使う方法がわからない…誰か氏〜〜)

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

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

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

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

 【JavaScript】「formをjsonにしてpost」する。  

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が送られていることを確認します

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をご用意ください。

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に包んでくれているので各自好きなようにしてください。

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します
これをすることでいい感じにバリデーションメッセージが生成されるようになります

HomeController.scala

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

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

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

完成(仮)

これでとりあえず、バリデーションメッセージを取得できるコードが完成しました。
form作成時にnonEmptyTextを指定したので、
空のフォームを送ってみます。
javaScriptのconsoleに以下が表示されていれば成功ですやった〜〜〜〜
スクリーンショット 2018-05-31 11.01.48.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}文字以下の文字数で入力ください

スクリーンショット 2018-05-31 11.26.28.png

やったね〜〜〜

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

エラーの生成

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)
        );
    });
});

スクリーンショット 2018-05-31 11.31.30.png

END:v:

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4