技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

フォームヘルパーをタグ調整する(Play 2.4.x) with マルチセレクトチェックボックス

Playframework 2.4 において、フィールドコンストラクタの仕様も変わったので、対応したチェックボックスを作ってみる。

Custom Field Constructors

これがなんなのかというと、input とかの helpers の外枠の事。
いつぞや Bootstrap 対応した フォームヘルパーをタグ調整する(Play 2.3.x) - 謎言語使いの徒然 を焼き直す。

Bootstrap インストール

まずは、いろいろやるのが面倒なので、build.sbt に下記を用意する。

libraryDependencies ++= Seq(
  "com.github.aselab" %% "scala-activerecord" % "0.3.1",
  "com.h2database" % "h2" % "1.4.185" % "test",
  "mysql" % "mysql-connector-java" % "5.1.34",

  // この下 3 行を追加
  "org.webjars" %% "webjars-play" % "2.4.0-1",
  "org.webjars" % "jquery" % "1.11.3",
  "org.webjars" % "bootstrap" % "3.3.5",
  evolutions,
  jdbc,
  cache,
  ws,
  specs2 % Test
)

// あとから書くのも面倒なので追加。
TwirlKeys.templateImports += "views.html.commons._"

そしたら、conf/routes に下記を追記

GET     /webjars/*file                    controllers.WebJarAssets.at(file)

app/view/main.scala.html も Bootstrap 取り込み。

@(title: String)(content: Html)

<!DOCTYPE html>

<html lang="en">
    <head>
        <title>@title</title>
        <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
        <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
        <!-- custom for bootstrap -->
        <link rel='stylesheet' href='@routes.WebJarAssets.at(WebJarAssets.locate("css/bootstrap.min.css"))'>
        <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("jquery.min.js"))'></script>
        <script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("js/bootstrap.min.js"))'></script>
    </head>
    <body>
        <!-- Wrap bootstrap container -->
        <div class="container-fluid">@content</div>
    </body>
</html>

跡はTodoリストでも勝手に書いて。
ちなみに、Bootstrap の入力フォームは、各 input タグに class="form-control" 入れないとデザイン整わないので注意。

カスタムフィールドコンストラクタの作成

と言っても言うほどの事もないか?
app/views/commons/bootstrapFields.scala.html 作って、下記を置く。

@(elements: helper.FieldElements)
<div class="form-group @if(elements.hasErrors){has-error}">
    <label for="@elements.id" class="col-sm-2 control-label">@elements.label</label>
    <div class="col-sm-10">
        @elements.input
    </div>
    @if(!elements.infos.isEmpty){
    <div class="col-sm-10 col-sm-offset-2">
    <span class="help-inline">
      @for(info <- elements.infos){
      <span class="label label-warning">@info</span>
      }
    </span>
    </div>
    }

    @if(elements.hasErrors) {
    <div class="col-sm-12">
        <div class="alert alert-danger">
            <ul>
                @for(msg <- elements.errors){
                <li>@msg</li>
                }
            </ul>
        </div>
    </div>
    }
</div>

そして、パッケージオブジェクトを作成する。

package views.html

package object commons {
  import views.html.helper.FieldConstructor
  implicit val bootStrapFieldConstructor = FieldConstructor(bootstrapFields.f)
}

import 設定は、build.sbt に入れてあるので、後は普通に helpers.input とか使うだけ。

マルチセレクトチェックボックス

デフォルトのヘルパーにいないので、頑張って作ってみた。
え?頑張ってないじゃないかって?実は問題が一つあるんだけど、後で話そう。

@(field: play.api.data.Field, keyValues: Map[String, String])(implicit messages: play.api.i18n.Messages)

@helper.input(field) { (id, name, value, htmlArgs) =>
    @defining(keyValues.toList.zipWithIndex){ results =>
        @for(kv_i <- results){
        <label class="checkbox-inline">
            <input id="@{id}_@{kv_i._2}" name="@{name}[@{kv_i._2}]" type="checkbox"
                   value="@{kv_i._1._2}" @if(value.exists(v => {keyValues.exists(_._2 == v)})){selected}> @kv_i._1._1
        </label>
        }
    }
}

使い方的には、フォームを下記の定義にして

  val checkBoxes = {
    import play.api.data.Forms._
    Form(single("multiSelect" -> list(longNumber)))
  }

テンプレート上でこう

@multipleCheckBox(checkBoxes("multiSelect"), Map("first" -> "1", "second" -> "2", "third" -> "3"))

そして、問題はなんなのかというと、バインドしたフォームを指定してもチェックが付かないという事。
解決しようと 3 時間くらい粘ったけど、結局未解決。

知ってる方は是非 scala - Playframework2.4 の Form で list(number) をバインドした時のフィールドコンストラクタ挙動で、value 値が取得できない。 - スタック・オーバーフロー

追記。

上記リンク先で、無事解決しました。

kawty さんありがとうございます。