[初心者向け] Spring MVC + JSP + Bootstrap 3 の使い方（Form編）

[前回](https://qiita.com/yoshikawaa/items/36957aa1dffcda2112f2)は、Spring MVC(TERASOLUNA 5)にBootstrap 3を適用する方法を解説しましたが、今回は入力フォームの実装方法を解説してみたいと思います。

アプリのベースとしては、TERASOLUNA 5のブランクプロジェクトを使用しますが、基本的にSpring MVCアプリには適用できます。

## 今回使用するライブラリ

* TERASOLUNA GFW 5.4.1.RELEASE
  - Spring MVC 4.3.14.RELEASE
* Bootstrap 3.3.7
* Bootstrap DatePicker 1.8.0
* JQuery Bootstrap 3.3.1

> 執筆時点で、Bootstrapの最新verは4.1.3です。HTMLに適用するクラス名に違いはありますが、基本的に同じノリで適用できるはずですが、マイグレーションが難しそうなら別に記事を書くかもしれません。

## （前置き）Bootstrapのパネルを適用する

入力フォームとは直接関係ありませんが、画面にパネルを適用することで、画面の表示項目を整理することができます。

```html
    <div id="wrapper" class="container-fluid">

        <div class="panel panel-default">
            <div class="panel-heading"><!-- 入力フォームのタイトル --></div>
            <div class="panel-body">
                <!-- ここに入力フォームを実装する -->
            </div>
        </div>
    </div>
```

**親`div`タグに`panel`クラスを付与すると、パネルが生成される。**
**子`div`タグに`panel-heading`クラスを付与すると、パネルのタイトルになる。**
**子`div`タグに`panel-body`クラスを付与すると、パネルのコンテンツになる。**

`panel-default`はパネルの色を指定するクラスで、以下の色が指定できます。
* `-primary`（青）
* `-success`（緑）
* `-info`（水色）
* `-warning`（黄色）
* `-danger`（赤）

以降は、パネルのコンテンツに入力フォームを実装していきます。`

## ベーシックな入力フォームを実装する

まずは、ベーシックな入力フォームを実装してみます。

* 期待するHTML

```html
<form method="post" action="/sample">

    <!-- (1) -->
    <div class="form-group">
        <label for="input" class="control-label">Input</label>
        <input id="input" name="input" class="form-control" placeholder="Input" />
        <span id="input.errors" class="help-block">Error Message</span>
    </div>

    <!-- (2) -->
    <button type="submit" class="btn btn-primary">Submit</button>
    <button type="reset" class="btn btn-default">Reset</button>

</form>
```

(1)
**入力項目のラベル・入力フィールド・エラーメッセージを`div`タグで囲み、`form-group`クラスを付与する。**
**ラベルに``control-label``クラスを付与する。**
**入力フィールドに``form-control``クラスを付与する。**
**エラーメッセージに``help-block``クラスを付与する。**

これを1セットとして、すべての入力項目に適用すると、Bootstrapにより入力項目がきれいに並べられます。

> 入力フィールドの`placeholder`で、入力例や入力条件を示しておくと、ユーザビリティがアップします。

(2)
**ボタンに`btn`クラスを付与する。**

これにより、ボタンやリンクをBootstrapのボタンデザインに統一することができます。

> ボタンの色と位置のルールを統一すると、アプリのアクセシビリティがアップします。
> * ポジティブなアクションのボタンは青系、ネガティブなアクションのボタンは赤系とか
> * ポジティブなアクションのボタンは右側、ネガティブなアクションのボタンは左側とか

* JSPの実装

```html
<!-- (1) -->
<form:form method="post" action="${pageContext.request.contextPath}/sample"
    modelAttribute="inputForm">

    <!-- (2) -->
    <div class="form-group">
        <form:label path="input" cssClass="control-label">Input</form:label>
        <form:input path="input" cssClass="form-control" placeholder="Input" />
        <form:errors path="input" cssClass="help-block" />
    </div>

    <!-- (3) -->
    <form:button class="btn btn-primary">Submit</form:button>
    <form:button class="btn btn-default" type="reset">Reset</form:button>

</form:form>
```

(1)
**`form:form`タグを利用して、モデルとバインドする。**

(2)
**`form:label`タグの`cssClass`に`control-label`クラスを付与する。**
**`form:input`タグの`cssClass`に`form-control`クラスを付与する。**
**`form:errors`タグの`cssClass`に`help-block`クラスを付与する。**

`id`、`name`は`path`から自動的に付与されます。

(3)
**`form:button`タグでは`class`に`btn`クラスを付与する。**

`form:button`タグには`cssClass`がないので、注意しましょう。
`type="submit"`は自動的に付与されるので、それ以外にしたいときだけ`type`を付与します。

### Inlineな入力フォームを実装する

すべての入力項目を一行で表現する入力フォームを実装します。

* 期待するHTML

```html
<form method="post" action="/sample" class="form-inline">
  ...
</form>
```

**`form`タグに`form-inline`クラスを付与する。**

これだけです。

* JSPの実装

```html
<form:form method="post" action="${pageContext.request.contextPath}/sample"
    modelAttribute="inputForm" class="form-inline">
  ...
</form:form>
```

JSPも同じように実装できます。

### Horizontalな入力フォームを実装する

ひとつの入力項目を一行で表現する入力フォームを実装します。

* 期待するHTML

```html
<!-- (1) -->
<form method="post" action="/sample" class="form-horizontal">

    <!-- (2) -->
    <div class="form-group">
        <label for="input" class="control-label col-sm-2">Input</label>
        <div class="col-sm-10">
            <input id="input" name="input" class="form-control" placeholder="Input" />
            <span id="input.errors" class="help-block">Error Message</span>
        </div>
    </div>

    <!-- (3) -->
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-primary">Submit</button>
            <button type="reset" class="btn btn-default">Reset</button>
        </div>
    </div>

</form>
```

(1)
**`form`タグに`form-horizontal`クラスを付与する。**

(2)
**`col-sm-2`クラスを付与して、ラベルと入力フィールドの表示位置を調整する。**

Bootstrapでは1行を12分割して表示位置を調整することができ、`col-sm-`の合計が12になるよう数字を調整するだけでOKです。
入力フィールドは`div`で囲んで`col-sm-`を付与する必要があることに注意しましょう。

(3)
**ボタンを`form-group`の`div`で囲み、さらに`col-sm-`の`div`で表示位置を調整する。**

`col-sm-offset-`は見たままですが、オフセットを指定してボタンを入力フィールドと並べます。

* JSPの実装

```html
<form:form method="post" action="${pageContext.request.contextPath}/sample"
    modelAttribute="inputForm"  class="form-horizontal">

    <div class="form-group">
        <form:label path="input" cssClass="control-label col-sm-2">Input</form:label>
        <div class="col-sm-10">
            <form:input path="input" cssClass="form-control" placeholder="Input" />
            <form:errors path="input" cssClass="help-block" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <form:button class="btn btn-primary">Submit</form:button>
            <form:button class="btn btn-default" type="reset">Reset</form:button>
        </div>
    </div>

</form:form>
```

JSPも同じように実装できます。

## 入力チェックエラー時にスタイルを変更する

入力チェックエラーになった入力項目（ラベル・入力フィールド・エラーメッセージ）を赤く表示してみます。

* 期待するHTML

```html
<form method="post" action="/sample">

    <!-- (1) -->
    <div class="form-group has-error">
        <label for="input" class="control-label">Input</label>
        <input id="input" name="input" class="form-control" placeholder="Input" />
        <span id="input.errors" class="help-block">Error Message</span>
    </div>

    <button type="submit" class="btn btn-primary">Submit</button>
    <button type="reset" class="btn btn-default">Reset</button>

</form>
```

(1)
**入力項目を囲む`div`タグに`has-error`クラスを付与する。**

これだけで、ラベル・入力フィールドの枠・エラーメッセージが赤く表示されます。
`control-label`、`form-control`、`help-block`クラスがついていないと色が変わらないので注意。

* JSPの実装

```html
<form:form method="post" action="${pageContext.request.contextPath}/sample"
    modelAttribute="inputForm">

    <!-- (1) -->
    <spring:hasBindErrors name="inputForm">
        <c:set var="errors" value="${errors}" />
    </spring:hasBindErrors>

    <!-- (2) -->
    <div class="form-group ${errors.hasFieldErrors('input') ? 'has-error' : ''}">
        <form:label path="input" cssClass="control-label">Input</form:label>
        <form:input path="input" cssClass="form-control" placeholder="Input" />
        <form:errors path="input" cssClass="help-block" />
    </div>

    <form:button class="btn btn-primary">Submit</form:button>
    <form:button class="btn btn-default" type="reset">Reset</form:button>

</form:form>
```

(1)
**`spring:hasBindErrors`タグでエラー情報を取得して、`c:set`タグでページ変数に登録する。**

入力チェックエラーかどうかを判定する必要がありますが、エラー情報を参照するためには一工夫必要です。

`spring:hasBindErrors`タグは、エラーがあるときだけ有効になり、そのタグ内でだけエラー情報を参照可能にするタグです。
`spring:hasBindErrors`タグをまっとうに利用して正常時とエラー時の画面を実装するのはかなり面倒なので、エラー情報だけいただいてフレキシブルなエラー判定を可能にします。

> これはControllerで`model.addAttribute("errors", bindingResult)`してるのと同じです。

(2)
**入力項目を囲む`div`タグでEL式を利用して、エラーのときだけ`has-error`クラスを付与する。**

`errors`はController引数で扱うのと同じ`BindingResult`オブジェクトです。
`BindingResult#hasFieldErrors(フィールド名)`で、入力チェックエラーかどうかを判定することができます。

### 入力チェックエラーメッセージをツールチップに表示する

エラーメッセージをツールチップに表示してみます。

通常、ツールチップを表示するには、入力フィールドに`data-toggle="tooltip"`と`title="メッセージ"`を付与しますが、Spring MVCではエラーメッセージを入力フィールドとは別に出力するので、自前でツールチップに表示してあげる必要があります。

* JSPの実装

```html
<form:form method="post" action="${pageContext.request.contextPath}/sample"
    modelAttribute="inputForm">

    <div class="form-group">
        <form:label path="input" cssClass="control-label">Input</form:label>
        <form:input path="input" cssClass="form-control" placeholder="Input" />
        <!-- (1) -->
        <form:errors path="input" cssClass="hidden" />
    </div>

    <form:button class="btn btn-primary">Submit</form:button>
    <form:button class="btn btn-default" type="reset">Reset</form:button>

</form:form>

...

<!-- (2) -->
<script type="text/javascript">
    $('.form-control').tooltip({
        html : true,
        title : function() {
            return $('#' + $(this).attr('id') + '\\.errors').html();
        }
    });
</script>


```

(1)
**`form:errors`タグに`hidden`クラスを付与し、エラーメッセージを非表示にする。**

(2)
**画面の最下部で、入力フィールドのツールチップを有効にします。**

`form-control`クラスが付与された入力フィールドにツールチップに、`form:errors`タグで出力したエラーメッセージをセットしています。
`form:errors`タグは、`[フィールド名].errors`というidでエラーメッセージを出力するので、idでフックしています。

## 入力部品

### 入力部品：select

select項目を実装します。

Bootstrap的には通常の入力フィールドと変わらないので、期待するHTMLは省略します。

* JSPの実装

```html
    <div class="form-group">
        <form:label path="select" cssClass="control-label">Select</form:label>
        <form:select path="select" cssClass="form-control">
            <form:option value="">Please Select...</form:option>
            <form:options items="${selectList}" />
        </form:select>
        <form:errors path="select" cssClass="help-block" />
    </div>
```

### 入力部品：チェックボックス

チェックボックス項目を実装します。

Spring MVCの`form:checkboxes`タグでは実現できないので、少々実装が面倒です。

* 期待するHTML

```html
    <div class="form-group">
        <!-- (1) -->
        <label class="control-label">CheckBox</label>
        <!-- (2) -->
        <label for="checkboxes1" class="checkbox-inline">
            <input type="checkbox" id="checkboxes1" name="checkboxes1" value="1">[1]
        </label>
        <label for="checkboxes2" class="checkbox-inline">
            <input type="checkbox" id="checkboxes2" name="checkboxes2" value="2">[2]
        </label>
        <label for="checkboxes3" class="checkbox-inline">
            <input type="checkbox" id="checkboxes3" name="checkboxes3" value="3">[3]
        </label>
        <span id="checkboxes.errors" class="help-block">Error Message</span>
    </div>
```

(1)
**チェックボックス項目全体のラベルは、入力フィールドに紐づけない。**

(2)
**入力フィールドをラベルで囲んで、`checkbox-inline`クラスを付与する。**

`label`タグの`for`と`input`タグの`id`がひとつずつリンクしているのがポイントです。

* JSPの実装

```html
    <div class="form-group">
        <!-- (1) -->
        <label class="control-label">CheckBox</label>
        <!-- (2) -->
        <c:forEach items="${selectList}" var="item" varStatus="status">
            <label for="checkboxes${status.count}" class="checkbox-inline">
                <form:checkbox path="checkboxes" value="${item.key}" />${item.value}
            </label>
        </c:forEach>
        <form:errors path="checkboxes" cssClass="help-block" />
    </div>
```

(1)
**チェックボックス項目全体のラベルは、`form:label`でなく`label`タグを使う。**

(2)
**`form:checkboxes`タグは使用せず、自前で繰り返してラベルと入力フィールドを出力する。**

`form:checkboxes`タグだと`label`タグにクラスを付与できないので、自前で出力する必要があります。
その際、`form:label`タグでは`for`にインデックスがつかないので、`label`タグを使う必要があります。

### 入力部品：ラジオボタン

ラジオボタン項目を実装します。

checkbox項目と同様に、Spring MVCの`form:radiobuttons`タグでは実現できないので、少々実装が面倒です。

* 期待するHTML

```html
    <div class="form-group">
        <!-- (1) -->
        <label class="control-label">Radio</label>
        <!-- (2) -->
        <label for="radio1" class="radio-inline">
            <input type="radio" id="radio1" name="radio" value="1">[1]
        </label>
        <label for="radio2" class="radio-inline">
            <input type="radio" id="radio2" name="radio" value="2">[2]
        </label>
        <label for="radio3" class="radio-inline">
            <input type="radio" id="radio3" name="radio" value="3">[3]
        </label>
        <span id="radio.errors" class="help-block">Error Message</span>
    </div>
```

(1)
**ラジオボタン項目全体のラベルは、入力フィールドに紐づけない。**

(2)
**入力フィールドをラベルで囲んで、`radio-inline`クラスを付与する。**

`label`タグの`for`と`input`タグの`id`がひとつずつリンクしているのがポイントです。

* JSPの実装

```html
    <div class="form-group">
        <!-- (1) -->
        <label class="control-label">Radio</label>
        <!-- (2) -->
        <c:forEach items="${selectList}" var="item" varStatus="status">
            <label for="radio${status.count}" class="radio-inline">
                <form:radiobutton path="radio" value="${item.key}" />${item.value}
            </label>
        </c:forEach>
        <form:errors path="radio" cssClass="help-block" />
    </div>
```

(1)
**ラジオボタン項目全体のラベルは、`form:label`でなく`label`タグを使う。**

(2)
**`form:radiobuttons`タグは使用せず、自前で繰り返してラベルと入力フィールドを出力する。**

`form:radiobuttons`タグだと`label`タグにクラスを付与できないので、自前で出力する必要があります。
その際、`form:label`タグでは`for`にインデックスがつかないので、`label`タグを使う必要があります。

### 入力部品：日付

日付項目を実装します。

Bootstrapのアシストライブラリ[Bootstrap DatePicker](https://bootstrap-datepicker.readthedocs.io/en/latest/)を利用すると、日付選択ダイアログを表示することができます。

Bootstrap DatePickerは、[CDN](https://cdnjs.com/libraries/bootstrap-datepicker)または[ダウンロード](https://github.com/uxsolutions/bootstrap-datepicker/releases)して参照します。WebJarsとしては提供されていないようです。

* JSPの実装

```html
<html>
<head>
<!-- BootstrapやJQueryの参照は省略します -->
<!-- (1) -->
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/app/css/bootstrap-datepicker3.min.css">
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/app/js/bootstrap-datepicker.min.js"></script>
</head>
<body>

    ...

    <div class="form-group">
        <form:label path="date" cssClass="control-label">Date</form:label>
        <!-- (3) -->
        <form:input path="date" cssClass="form-control date" placeholder="Date" />
        <form:errors path="date" cssClass="help-block" />
    </div>

    ...

    <!-- (2) -->
    <script type="text/javascript">
        $('.date').datepicker({
            format : 'yyyy-mm-dd'
        });
    </script>

</body>
</html>
```

(1)
**Bootstrap DatePickerへの参照を追加します。**

ここでは、事前にダウンロードしたCSS・JSファイルを参照しています。

(2)
**画面の最下部で、Bootstrap DatePickerを有効にします。**

ここでは、`date`クラスが付与された項目に対してBootstrap DatePickerを有効にしています。

HTMLの特定の要素に紐づけるため、`script`タグは`body`タグの終わりに書く必要があります。
オプションで日付フォーマットを指定できるので、入力項目をバインドするフィールドの`@DateTimeFormat`と一致させましょう。

> 他に、選択する日付を制限できたり、様々な言語に対応していたりします。

(3)
**入力フィールドを、Bootstrap DatePickerを有効にする条件と合致させます。**

(2)に従い、`date`クラスを付与しています。

## まとめ

今回は、Spring MVC(TERASOLUNA 5)アプリでBootstrap 3を利用した入力フォームを実装する方法をまとめてみました。

BootstrapのようなCSSフレームワークを使うと、手軽に画面デザインを洗練することができて良いのですが、Spring MVC＋JSPのように一部のHTMLを自動的に構築してくれるフレームワークとはかみ合わない場合が多々あります。

このような問題を解決するポイントは、CSSフレームワークとAPフレームワーク双方が意図するHTMLの構造を把握することにあります。これを踏まえて、CSSフレームワークを使用するかどうかを検討すると良いですね。

次回は、Bootstrap 3を利用したテーブル表示の実装方法についてまとめます。

## 参考資料

* [Bootstrap 3 Docs / CSS / Forms](https://getbootstrap.com/docs/3.3/css/#forms)
* [Bootstrap 3 Docs / Components / Panels](https://getbootstrap.com/docs/3.3/components/#panels)
* [Bootstrap DatePicker Docs](https://bootstrap-datepicker.readthedocs.io/en/latest/#)