まえがき
今回作成したコードはgithubにあげております
・ https://github.com/agoetc/play-form-example
目標
ページ遷移なしでいい感じにバリデーションをかけたい
バージョン
- sbt 1.2.8
- Scala 2.13.0
- PlayFramework 2.7.3
フォームのPOST
今回はユーザーの登録を仮定して実装していきます。
まず、formで使用するcontrollerと、そのrouteを作成します。
@Singleton
class HomeController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
// フォームのあるページを表示するよ
def index = Action {
Ok(views.html.index())
}
// ここでjsonを受け取ってバリデーションかけたいよ
def post = Action {
Ok("")
}
}
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が必要らしいので引数に入れましょう
@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>
}
play.filters.headers.contentSecurityPolicy = null
formの情報は$('form').serializeArray()
で手に入れることができますが、
[
{
"name": "name",
"value": "入力値"
}, {
"name": "kana",
"value": "入力値"
}
]
という辛い形ですので自力で形式を変更していきます。
下記の記事を参考にさせていただきました(というかもろパクリ)ので
詳しくはこちらをお読みください。
[【JavaScript】「formをjsonにしてpost」する。]
(https://qiita.com/hnkyi/items/3f2faf2848cc2de0cf8c)
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が送られていることを確認します
def post = Action { implicit request =>
// Option[JsValue]
val jsonOpt = request.body.asJson
println(jsonOpt)
Ok("")
}
ここまで記述し、フォームを送信すると
sbt consoleにSome({"name":"入力値","kana":"入力値"})
が表示されていることが確認できました。
バリデーション
case classとFormをご用意ください。
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に包んでくれているので各自好きなようにしてください。
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します
これをすることでいい感じにバリデーションメッセージが生成されるようになります
class HomeController @Inject()(
cc: ControllerComponents,
messagesAction: MessagesActionBuilder
)
extends AbstractController(cc) {
/* --------------------------------
* 中略
* --------------------------------
*/
def post = messagesAction { implicit request =>
/* --------------------------------
* 以下略
* --------------------------------
*/
完成(仮)
これでとりあえず、バリデーションメッセージを取得できるコードが完成しました。
form作成時にnonEmptyTextを指定したので、
空のフォームを送ってみます。
javaScriptのconsoleに以下が表示されていれば成功ですやった〜〜〜〜
…英語じゃん?
バリデーションメッセージを指定します
バリデーションメッセージの変更
デフォルトでは、conf/messages
を変更すればいいです。
conf/application.conf
で
play.i18n.langs
をja等に設定している人は
conf/messages.ja
のように作成しましょう
error.required=入力してください
error.number=数字を入力してください
error.min={0}より大きい数字を入力ください
error.max={0}より小さい数字を入力ください
error.minLength={0}文字以上入力ください
error.maxLength={0}文字以下の文字数で入力ください
やったね〜〜〜
ついでにエラーをhtmlに生成します
エラーの生成
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)
);
});
});
END