かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その49 )( 入力画面3を作成する2 )

概要

記事一覧はこちらです。

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その48 )( 入力画面3を作成する ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 入力画面3の作成
    • 「アンケート」の項目には、DB に専用のテーブルを作成して、そこに登録されたデータから動的に表示する処理を実装します。

参照したサイト・書籍

  1. Conditionally closing tag in Thymeleaf
    https://stackoverflow.com/questions/36747620/conditionally-closing-tag-in-thymeleaf

目次

  1. アンケートに表示する項目を定義するテーブルを Database Tools で作成する
  2. 作成した create table 文を Flyway 用の SQL ファイル V1__init.sql に追加する
  3. Doma-Gen で entity クラス, dao インターフェースを生成する
  4. SurveyOptionsHelper クラスを作成する
  5. SurveyOptionsHelper クラスのテストを作成する
  6. input03.html を修正する&動作確認

手順

アンケートに表示する項目を定義するテーブルを Database Tools で作成する

Spring Boot + npm + Geb で入力フォームを作ってテストする ( その16 )( H2 Database に Flyway でテーブルを作成する ) を参考に、Tomcat を起動して Database Tools で in-memory モードの H2 Database に接続し、PUBLIC スキーマを選択してコンテキストメニューを表示した後、「New」-「Table」を選択します。

「Create New Table」ダイアログが表示されるので、以下の画像のようにテーブルを定義します。

f:id:ksby:20180418010743p:plain

ダイアログの下の「SQL Script」に生成された SQL は以下のようになります。

CREATE TABLE SURVEY_OPTIONS
(
    group_name VARCHAR(16) NOT NULL,
    item_value VARCHAR(1) NOT NULL,
    item_name VARCHAR(64) NOT NULL,
    item_order INT NOT NULL,
    CONSTRAINT PK_SURVEY_OPTIONS PRIMARY KEY (group_name, item_value)
);

「Execute」ボタンをクリックするとテーブルが作成されて、Database Tools 上に SURVEY_OPTIONS テーブルが表示されます。

f:id:ksby:20180418010940p:plain

作成した create table 文を Flyway 用の SQL ファイル V1__init.sql に追加する

作成した create table 文とデータを投入する insert 文を src/main/resources/db/migration/V1__init.sql の中に記述します。

CREATE TABLE INQUIRY_DATA
(
    ..........
);

CREATE TABLE SURVEY_OPTIONS
(
  group_name VARCHAR(16) NOT NULL,
  item_value VARCHAR(1) NOT NULL,
  item_name VARCHAR(64) NOT NULL,
  item_order INT NOT NULL,
  CONSTRAINT PK_SURVEY_OPTIONS PRIMARY KEY (group_name, item_value)
);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '1', '選択肢1だけ長くしてみる', 1);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '2', '選択肢2', 2);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '3', '選択肢3', 3);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '4', '選択肢4', 4);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '5', '選択肢5が少し長い', 5);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '6', '選択肢6', 6);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '7', '選択肢7', 7);
INSERT INTO SURVEY_OPTIONS VALUES ('survey', '8', '8', 8);

Tomcat を再起動すると SURVEY_OPTIONS テーブルが作成されてデータもセットされていることが確認できます。

f:id:ksby:20180418013514p:plain f:id:ksby:20180418013605p:plain

Doma-Gen で entity クラス, dao インターフェースを生成する

build.gradle の domaGen タスクを変更し、Tomcat を起動した状態で entity クラス, dao インターフェースを生成します。

build.gradle を以下のように変更します。

// for Doma-Gen
task domaGen {
    doLast {
        // まず変更が必要なもの
        def rootPackageName = 'ksbysample.webapp.bootnpmgeb'
        def rootPackagePath = 'src/main/java/ksbysample/webapp/bootnpmgeb'
        def dbUrl = 'jdbc:h2:tcp://localhost:9092/mem:bootnpmgebdb'
        def dbUser = 'sa'
        def dbPassword = ''
        def tableNamePattern = 'SURVEY_OPTIONS'
        // おそらく変更不要なもの
        ..........
  • domaGen タスクの以下の点を変更します。
    • tableNamePattern.*SURVEY_OPTIONS に変更します。

Gradle Tool Window から domaGen タスクを実行します。"BUILD SUCCESSFUL" が表示されました。

f:id:ksby:20180423234132p:plain

Project Tool Window を見ると SurveyOptionsDao インターフェース、SurveyOptions クラスが作成されています。

f:id:ksby:20180423234422p:plain

domaGen タスクの tableNamePattern.* に戻します。

SurveyOptionsHelper クラスを作成する

SURVEY_OPTIONS テーブルのデータを取得して Thymeleaf テンプレートに出力するための SurveyOptionsHelper クラスを作成します。

まずは src/main/java/ksbysample/webapp/bootnpmgeb/dao/SurveyOptionsDao.java の以下の点を変更します。

@Dao
@ComponentAndAutowiredDomaConfig
public interface SurveyOptionsDao {

    /**
     * @param groupName groupName
     * @param itemValue itemValue
     * @return the SurveyOptions entity
     */
    @Select
    SurveyOptions selectById(String groupName, String itemValue);

    /**
     * 指定されたグループ名のレコードのリストを取得する
     *
     * @param groupName グループ名
     * @return {@SurveyOptions} エンティティのリスト
     */
    @Select
    List<SurveyOptions> selectByGroupName(String groupName);

    ..........
  • selectByGroupName メソッドを追加します。

src/main/resources/META-INF/ksbysample/webapp/bootnpmgeb/dao/SurveyOptionsDao/selectByGroupName.sql を新規作成し、以下の内容を記述します。

select
  /*%expand*/*
from
  SURVEY_OPTIONS
where
  GROUP_NAME = /* groupName */'survey'
order by
  ITEM_ORDER

ksbysample.webapp.bootnpmgeb.helper の下に db パッケージを新規作成します。その下に SurveyOptionsHelper.java を新規作成し、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.helper.db;

import ksbysample.webapp.bootnpmgeb.dao.SurveyOptionsDao;
import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * SURVEY_OPTIONS テーブルデータ取得用 Helper クラス
 */
@Component("soh")
public class SurveyOptionsHelper {

    private final SurveyOptionsDao surveyOptionsDao;

    /**
     * コンストラクタ
     *
     * @param surveyOptionsDao {@SurveyOptionsDao} オブジェクト
     */
    public SurveyOptionsHelper(SurveyOptionsDao surveyOptionsDao) {
        this.surveyOptionsDao = surveyOptionsDao;
    }

    /**
     * SURVEY_OPTIONS テーブルから指定されたグループ名のリストを取得する
     *
     * @param groupName グループ名
     * @return {@SurveyOptions} オブジェクトのリスト
     */
    public List<SurveyOptions> selectItemList(String groupName) {
        List<SurveyOptions> surveyOptionsList = surveyOptionsDao.selectByGroupName(groupName);
        if (CollectionUtils.isEmpty(surveyOptionsList)) {
            throw new IllegalArgumentException("指定されたグループ名のデータは登録されていません");
        }
        return surveyOptionsList;
    }

}

SurveyOptionsHelper クラスのテストを作成する

src/main/java/ksbysample/webapp/bootnpmgeb/helper/db/SurveyOptionsHelper.java で Ctrl+Shift+T を押してコンテキストメニューを表示した後、「Create New Test...」を選択します。

src/test/groovy/ksbysample/webapp/bootnpmgeb/helper/db/SurveyOptionsHelperTest.groovy が新規作成されますので、以下の内容を記述します。

package ksbysample.webapp.bootnpmgeb.helper.db

import ksbysample.webapp.bootnpmgeb.entity.SurveyOptions
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

@SpringBootTest
class SurveyOptionsHelperTest extends Specification {

    @Autowired
    private SurveyOptionsHelper soh

    def "登録されているグループ名を指定してselectItemListメソッドを呼ぶとリストが取得できる"() {
        setup:
        List<SurveyOptions> surveyOptionsList = soh.selectItemList("survey")

        expect:
        surveyOptionsList.size() == 8
        surveyOptionsList[0].itemValue == "1"
        surveyOptionsList[0].itemName == "選択肢1だけ長くしてみる"
        surveyOptionsList[7].itemValue == "8"
        surveyOptionsList[7].itemName == "8"
    }

    def "登録されていないグループ名を指定してselectItemListメソッドを呼ぶとIllegalArgumentExceptionがthrowされる"() {
        when:
        List<SurveyOptions> surveyOptionsList = soh.selectItemList("notexists")

        then:
        def e = thrown(IllegalArgumentException)
        e.getMessage() == "指定されたグループ名のデータは登録されていません"
    }

}

テストを実行して成功することを確認します。

f:id:ksby:20180424005934p:plain

input03.html を修正する&動作確認

「アンケート」の項目を SurveyOptionsHelper クラスを利用して SURVEY_OPTIONS テーブルのデータを出力するように src/main/resources/templates/web/inquiry/input03.html を以下のように変更します。

            <div class="form-group" id="form-group-survey">
              <div class="control-label col-sm-2">
                <label class="float-label">アンケート</label>
              </div>
              <div class="col-sm-10" id="multiline-checkbox">
                <th:block th:each="surveyOptions,iterStat : ${@soh.selectItemList('survey')}">
                  <th:block th:if="${iterStat.index % 3 == 0}">
                  <div class="row"><div class="col-sm-12">
                    <div class="checkbox">
                  </th:block>

                  <label><input type="checkbox" name="survey" th:value="${surveyOptions.itemValue}">
                    <th:block th:text="${surveyOptions.itemName}">選択肢1だけ長くしてみる</th:block>
                  </label>

                  <th:block th:if="${iterStat.index % 3 == 2 || iterStat .last}">
                    </div>
                  </div></div>
                  </th:block>
                </th:block>
              </div>
            </div>

Tomcat を起動して入力画面3を表示してみましたが、ボタンの表示位置がおかしいですね。。。

f:id:ksby:20180425115119p:plain

html を見ると、最初の <th:block th:if="${iterStat.index % 3 == 0}">...</th:block> は正常に動作していますが、後の <th:block th:if="${iterStat.index % 3 == 2 || iterStat.last}"></div>...</th:block> が最後しか出力されておらず正常に動作していませんでした。</th:block> も最後に2つそのまま出力されています。どうも Thymeleaf では閉じタグ側を動的に出力させる書き方ができないようです。

            <div class="form-group" id="form-group-survey">
              <div class="control-label col-sm-2">
                <label class="float-label">アンケート</label>
              </div>
              <div class="col-sm-10" id="multiline-checkbox">
                  <div class="row"><div class="col-sm-12">
                    <div class="checkbox">
                  <label><input type="checkbox" name="survey" value="1">
                    選択肢1だけ長くしてみる
                  </label>
                  <label><input type="checkbox" name="survey" value="2">
                    選択肢2
                  </label>
                  <label><input type="checkbox" name="survey" value="3">
                    選択肢3
                  </label>

                  <div class="row"><div class="col-sm-12">
                    <div class="checkbox">
                  <label><input type="checkbox" name="survey" value="4">
                    選択肢4
                  </label>
                  <label><input type="checkbox" name="survey" value="5">
                    選択肢5が少し長い
                  </label>
                  <label><input type="checkbox" name="survey" value="6">
                    選択肢6
                  </label>

                  <div class="row"><div class="col-sm-12">
                    <div class="checkbox">
                  <label><input type="checkbox" name="survey" value="7">
                    選択肢7
                  </label>
                  <label><input type="checkbox" name="survey" value="8"></label>
                    </div>
                  </div></div>
                  </th:block>
                </th:block>
              </div>

stackoverflow で調べてみると Conditionally closing tag in Thymeleaf というページを見つけました。今回は th:utext を使用する方法に変更してみます。

src/main/resources/templates/web/inquiry/input03.html を以下のように変更します。

            <div class="form-group" id="form-group-survey">
              <div class="control-label col-sm-2">
                <label class="float-label">アンケート</label>
              </div>
              <div class="col-sm-10" id="multiline-checkbox">
                <th:block th:each="surveyOptions,iterStat : ${@soh.selectItemList('survey')}">
                  <th:block th:if="${iterStat.index % 3 == 0}"
                            th:utext="'&lt;div class=&quot;row&quot;&gt;&lt;div class=&quot;col-sm-12&quot;&gt;&lt;div class=&quot;checkbox&quot;&gt;'"/>

                  <label><input type="checkbox" name="survey" th:value="${surveyOptions.itemValue}">
                    <th:block th:text="${surveyOptions.itemName}">選択肢1だけ長くしてみる</th:block>
                  </label>

                  <th:block th:if="${iterStat.index % 3 == 2 || iterStat.last}"
                            th:utext="'&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;'"/>
                </th:block>
              </div>
            </div>
  • 上下の条件を満たした時に div タグを出力する処理を <th:block th:if="..." th:utext="..."/> の形式に変更します。
  • th:utext の中に書く HTML 文は <&lt; へ、>&gt; へ、&quot; へ変換します。

今度は画面も問題なく表示されました。

f:id:ksby:20180425122931p:plain

html を見てもきちんと閉じタグが出力されています。

            <div class="form-group" id="form-group-survey">
              <div class="control-label col-sm-2">
                <label class="float-label">アンケート</label>
              </div>
              <div class="col-sm-10" id="multiline-checkbox">
                  <div class="row"><div class="col-sm-12"><div class="checkbox">
                  <label><input type="checkbox" name="survey" value="1">
                    選択肢1だけ長くしてみる
                  </label>
                  <label><input type="checkbox" name="survey" value="2">
                    選択肢2
                  </label>
                  <label><input type="checkbox" name="survey" value="3">
                    選択肢3
                  </label>
                  </div></div></div>

                  <div class="row"><div class="col-sm-12"><div class="checkbox">
                  <label><input type="checkbox" name="survey" value="4">
                    選択肢4
                  </label>
                  <label><input type="checkbox" name="survey" value="5">
                    選択肢5が少し長い
                  </label>
                  <label><input type="checkbox" name="survey" value="6">
                    選択肢6
                  </label>
                  </div></div></div>

                  <div class="row"><div class="col-sm-12"><div class="checkbox">
                  <label><input type="checkbox" name="survey" value="7">
                    選択肢7
                  </label>
                  <label><input type="checkbox" name="survey" value="8"></label>
                  </div></div></div>
              </div>
            </div>

履歴

2018/04/25
初版発行。